Thursday 26 November 2020

Forty-eight hours with Red Airship

This is about the time I spent forty-eight hours with the company known as Red Airship.

OK, nothing quite that dramatic. But I did commit myself to a two-day tryout as a developer.

Here's some backstory...

Early this year, my contract with my then-company was running out in a few months, after nearly three years at the job. And not being one to take anything for granted, I did what I always do when not guaranteed stable employment - look for jobs. As it happened, an ex-colleague on LinkedIn posted a want ad for a developer. I got in contact, and secured an interview.

Red Airship was a startup in Pasir Panjang and they were looking for someone to join a team in Tampines. Friggin' Tampines, all the way at the other end of the island. It was certainly off-putting, but distance never stopped me before. Then again, I was never forty-two years old before, either. Still, as a matter of principle, I showed up and did my best. The CTO Ryan Tan put me through two hours of what felt less like an interview and more like an informal chat.

Pleased to meet you.

At the end of the interview, we agreed to put all talks of employment on hold and just enjoy the upcoming Chinese New Year first. He also posed me a challenge - to learn as much about ReactJS as I could in the meantime. As it happened, I had been dabbling. (What else is new, right?) One of the things I had been dabbling in, was ReactJS, mostly because it seemed to be the go-to requirement of every other tech job ad out there.

Over the next few months, I kept in touch with Ryan, occasionally showing him stuff I'd made in ReactJS, and soliciting feedback. The agreement to discuss employment opportunities at a later date never happened. COVID-19 got in the way and it was a huge mess for everyone. I ended up joining some other company, but we still kept in touch.

Back to Red Airship

My experience in my new company did not turn out the way I expected, and within months I was seeking new employment. This led me to speak to Ryan again, and he proposed a two-day tryout at Red Airship. Due to the COVID-19 situation, this would be done remotely, through video conferencing. I would have to take two days' leave. This wasn't actually such a big deal. COVID-19 was still out there; what else was I gonna do with two days' leave - take a vacation overseas? Pfft.

No more vacations.

Besides, at that point I was experiencing some serious dissatisfaction with my current company. Never mind two days, I would have happily quit my job and tried out for two weeks. Free of charge.

The First Day

The weekend was spent watching YouTube tutorials on Redux and TypeScript, and installing the necessary software. When the day arrived, I hoped I was ready.

Via a video call, Ryan introduced me to the rest of Red Airship's team. I can't recall their names and certainly not their faces. There was some lady whose husband had served in the Singapore Navy, like myself. There was a guy who loved watching movies. And a bookish-looking dude whom I suspect I might have physically resembled thirty years ago. The last one I remember because it was his code that I was going to be working on later.

What happened next was nothing I'd ever seen before.

Something like this!

The image of a wheel-spinning script, with all our names on the wheel, was shared on-screen. When the wheel was spun, it would point to one of the names, and the corresponding member of the meeting would then proceed to talk about his or her weekend. It's precisely that kind of frivolity that reminds me why I stayed so long in this industry. Because code can be fun.

I suppose this was what counted for team-bonding in Red Airship. Seemed legit, I guess. The intention was certainly there. To be honest, I couldn't help but space out after an hour of this. Hopefully, it wasn't extremely noticeable.

From there, Ryan took me aside and informed me that what had just transpired was all about selling myself to the team - because after this they would put it to the vote and they would decide if they wanted to work with me. Again, this was a new concept, and I like it - it adds a new dimension to every team member's input. Usually, new hires are at the sole discretion of Management. Allowing team members to have a say wasn't something I encountered often.

Next, Ryan showed me an existing app that had been written by one of the guys. I had to ask questions about the code. Cool; my very first professionally-written ReactJS app. All I'd ever seen up to this point were examples on tutorial sites and StackOverflow. I was introduced to certain concepts in ReactJS, and certain concepts particular to ECMAScript, which, to my chagrin, I was supposed to have learned years ago but never used.

The day ended early, with Ryan telling me to go through the entire app and pick out questions I had about the code. I had plenty; and wrote them all down. However, it also occurred to me I could just as easily Google the answers myself. And that certainly worked for a few of those questions. Later that evening, I went out for a movie with the wife, and by the time I returned, through no real conscious effort on my part, I'd figured out the answers to a few more.

The Second Day

The second day began with Ryan going through the list of questions I'd prepared in a document. After a bit of clarification, it was time to test how much I'd absorbed. I was tasked to make changes to the code in order to cater to new requirements. And in this respect, Ryan was like a drill sergeant, battering me with searching questions (which I recognized as a form of didactic questioning due to my stint in the Institute of Lifelong Learning) instead of answering my questions directly. It had a very frantic feel.

Ryan told me he wasn't trying to be tough on me, but he expected his engineers to have the initiative to question and search for answers. That's fair. Though I wonder why exactly he thought he was being "tough". I've seen "tough". This wasn't even close.

Damn, I'm lost.

To be honest, my thought processes was slowing down somewhat and I was getting a bit lost, especially when it came to tests. Later in the evening (probably a bit too late by then), it occurred to me that I should have adjusted tests with every change I made, so I wouldn't lose track of what I'd done. And, ideally, committed the code. Commit early, commit often, I always say.

As it was, I was making alterations to the code with every new requirement, so when the time came to produce tests for those changes, I'd already forgotten what exactly I did earlier. Which, admittedly, didn't exactly leave a good impression. At one point, Ryan remarked that seeing me struggle was "painful", and I had to agree with that assessment. Where ReactJS was concerned, I'm primarily a hobbyist. Enthusiasm on its own doesn't translate to proficiency without proper direction.

And boy, was I getting that direction today.

Somehow or other, we managed to make it work. Tests were now running properly, and things were falling into place. Ryan got me to commit the code, and that was when I realized that these were real scenarios - he wasn't asking me to make those changes purely for the heck of it. I was actually doing work for Red Airship. Some might complain that it's a cheap way to get work for free, but I disagree. Had Ryan passed this on to one of his engineers to handle, it wouldn't have taken two days. All I'd done was put in some work for all the tutelage I'd received. And it wasn't even that much.

Then it was time for the post-mortem. Ryan thanked me for my time, but let's be real here: I was even more thankful than he was. I had everything to gain from this experience, and nothing to lose. If I didn't get a job with Red Airship, I would still have had the benefit of two entire days of training from actual ReactJS professionals. On their part, if Red Airship found me unsuitable for the job, they would have wasted two days.

Epilogue

I didn't get the job with Red Airship. Ryan informed me that the team "didn't feel the chemistry". Which is fine; I hadn't really expected to impress anyone on the back of that showing.

A month from those two days, my company and I parted ways and I joined another outfit.

Final Thoughts

Red Airship's method of fostering a bond between team-mates is interesting.

Personally, while I'm all for the concept, my experience with team-bonding has tended to be somewhat more organic, and less company-mandated. Usually, my colleagues and I start off with being cautiously polite, and perhaps within a month, we're getting on like a house on fire... regardless of nationality, age or gender. I pride myself on being rather more sociable than the stereotypical tech geek. Thus, I found myself ambivalent to Red Airship's approach, but I'm sure it works for them.

I appreciate the fact that Red Airship took a full two days to show me the ropes despite the fact that there was a very real chance that they would end up with very little to show for it. There's the culture of sharing right there.

On my part, this experience was valuable. Sure, I didn't get the job, but that was always secondary. I gained real insight into how to develop a ReactJS app, and to this day that sample code is in my possession. I'm still studying it, modelling my practices on it, and I've identified certain glaring gaps in my knowledge that I can plug, and work processes that I can refine.

I gained way more from Red Airship in these two days than they did from me, and that's a fact that deserves recognition. From developer to developer, I offer heartfelt thanks.

Fly high, airship.
T___T

Saturday 21 November 2020

Spot The Bug: Whiling Away Your Data

They're bad, they're mind-boggling, they're bugs! And we're here today to catch another one of those sneaky little buggers in the latest installment of Spot The Bug.

I know you're
out there, bugs.


So lately, I've been fooling around with WordPress. Don't ask me why; that's neither here nor there for today. Also, this particular bug had nothing to do with WordPress and everything to do with PHP. You'll see why in a bit.

So I had set up a dummy WordPress site in a Mac environment with a whole bunch of Lorem Ipsum. I was poking around WordPress's database schema using MySQL and wrote a little bit of PHP code in my local environment to list out the titles of the wp_posts table.
<?php
    $conn = new mysqli("127.0.0.1", "root", "", "wptest");

    if ($conn->connect_error)
    {
        die("DB connection failed.");
    }

    $sql = "SELECT * FROM wp_posts WHERE post_type='post' AND post_status='publish' ORDER BY post_date DESC";
    $result = $conn->query($sql);

    if (mysqli_num_rows($result) === 0) die("No posts!");
    
    while($row = $result->fetch_assoc())
    {    
        echo "<h1>" . $row["post_title"] . "</h1>";            
    }            

    $conn->close();
?>


So it was going swimmingly so far.

And then next I decided to list out the comments on each post, from the wp_comments table. A little nested While loop should do the trick.
<?php
    $conn = new mysqli("127.0.0.1", "root", "", "wptest");

    if ($conn->connect_error)
    {
        die("DB connection failed.");
    }

    $sql = "SELECT * FROM wp_posts WHERE post_type='post' AND post_status='publish' ORDER BY post_date DESC";
    $result = $conn->query($sql);

    if (mysqli_num_rows($result) === 0) die("No posts!");
    
    while($row = $result->fetch_assoc())
    {    
        echo "<h1>" . $row["post_title"] . "</h1>";

        $sql = "SELECT * FROM wp_comments WHERE comment_post_id = " . $row["ID"];

        $result = $conn->query($sql);

        if (mysqli_num_rows($result) === 0) break;

        while($row_comments = $result->fetch_assoc())
        {
            echo "<p>" . $row_comments["comment_content"] . "</p>";
        } 
           
    }            

    $conn->close();
?>


And this happened! The first post title and its comments were laid out. No errors were seen, but the rest of the content disappeared!

What went wrong

As it turned out, it was me who was being an idiot. I'd appended "_comments" to the name of the associative array, row, to avoid confusing the program, right? Well, I forgot to do the same for result.
$result = $conn->query($sql);

if (mysqli_num_rows($result) === 0) break;

while($row_comments = $result->fetch_assoc())
{
    echo "<p>" . $row_comments["comment_content"] . "</p>";
}


Why it went wrong

So if result, within the very first iteration of the outer While loop, became the object that contained the dataset of the comments of the first post, then quite naturally there would be no next post to fetch!
while($row = $result->fetch_assoc())
{    
    echo "<h1>" . $row["post_title"] . "</h1>";

    $sql = "SELECT * FROM wp_comments WHERE comment_post_id = " . $row["ID"];

    $result = $conn->query($sql);

    if (mysqli_num_rows($result) === 0) break;

    while($row_comments = $result->fetch_assoc())
    {
        echo "<p>" . $row_comments["comment_content"] . "</p>";
    }            
}


How I fixed it

Quite easily done! Just add "_comments" to the name of result!
<?php
    $conn = new mysqli("127.0.0.1", "root", "", "wptest");

    if ($conn->connect_error)
    {
        die("DB connection failed.");
    }

    $sql = "SELECT * FROM wp_posts WHERE post_type='post' AND post_status='publish' ORDER BY post_date DESC";
    $result = $conn->query($sql);

    if (mysqli_num_rows($result) === 0) die("No posts!");
    
    while($row = $result->fetch_assoc())
    {    
        echo "<h1>" . $row["post_title"] . "</h1>";

        $sql = "SELECT * FROM wp_comments WHERE comment_post_id = " . $row["ID"];

        $result_comments = $conn->query($sql);

        if (mysqli_num_rows($result_comments) === 0) break;

        while($row_comments = $result_comments->fetch_assoc())
        {
            echo "<p>" . $row_comments["comment_content"] . "</p>";
        }            
    }            

    $conn->close();
?>


And there you go. That beautiful, beautiful Lorem Ipsum.


Moral of the story

In nested loops of any kind, repeating the declaration of a variable that was used in the outer loop is a very bad idea. Even if nothing untoward occurs now, it's an accident waiting to happen.

Thanks for reading. _comments welcome!
T___T

Monday 16 November 2020

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

Nice work, guys. Stuff is working. All we really need to do to complete the job is to clean up the layout, add content, and populate the rest of the grid. Let's get to it.

Populating the grid

Right now, only the letters from the hidden words are shown, which makes the game easy to test but also makes it almost impossible to lose. So let's tweak the script to add in random letters. This will be part of the populateGrid() method. Add in a nested For loop to get to every element in the squares array.
populateGrid: function ()
{
    $(".wordgrid").html("");
    this.squares = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        this.squares[x] = [];

        for (var y = 0; y < this.maxSize; y++)
        {
            this.squares[x].push
            (
                {
                    letter: "",
                    selected: false,
                    correct: false,
                    dir: undefined,
                    intersected: false
                }
            );

            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);
                }
            );

            $(".wordgrid").append(square);
        }    
    }

    var unassigned = [];
    var i;
    do
    {
        this.populateGridWithWords();

        unassigned = this.words.filter((x) => {return !x.assigned;});
    }
    while (unassigned.length > 0);

    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {

        }    
    }  
                     
},


If the letter property is an empty string, generate a random character from the ASCII table. Set the letter property to that value, then use jQuery to ensure that the letter is in the grid at that position.
for (var x = 0; x < this.maxSize; x++)
{
    for (var y = 0; y < this.maxSize; y++)
    {
        if (this.squares[x][y].letter == "")
        {
            var randomChar = String.fromCharCode(this.generateRandomNo(25) + 97);
            this.squares[x][y].letter = randomChar;
            $(".letter[data-x=" + x + "][data-y=" + y + "]").html(randomChar);

        }
    }    
}


Ah, there it is... a fully-populated grid.



Cleaning up

Now all we need to do is get rid of the red lines.
div {outline: 0px solid #FF0000;}


Like magic!



Adding content

This is probably the easiest part. All throughout this web tutorial, we've been coding in a way that allows for new content to be added without needing to change anything outside of where the content is added.

So go to the themes array and insert more objects.
let themes =
[
    {
        title: "Sea Life",
        picture: "sealife",
        description: "Look for all life you can find in the oceans.",
        words: ["shark", "coral", "whale", "starfish", "jellyfish", "turtle", "anemone", "octopus", "swordfish", "squid", "lobster", "seahorse", "walrus", "lamprey", "prawn", "oyster", "barracuda", "mackerel", "clownfish", "plankton"]
    },
    {
        title: "Birds",
        picture: "birds",
        description: "Look for all kinds of birds.",
        words: ["pelican", "kingfisher", "woodpecker", "swallow", "sparrow", "eagle", "hummingbird", "cuckoo", "robin", "raven", "penguin", "pigeon", "albatross", "ostrich", "goose", "flamingo", "nightingale", "thrush", "crane", "heron"]
    },
    {
        title: "Food",
        picture: "food",
        description: "Look for anything you can eat.",
        words: ["butter", "bread", "yoghurt", "mutton", "pudding", "cereal", "steak", "noodles", "porridge", "salad", "sandwich", "omelette", "cupcake", "bacon", "cupcake", "chocolate", "sugar", "pancake", "pizza", "burger"]
    },
    {
        title: "Sports",
        picture: "sports",
        description: "Look for anything related to sports such as people, places or equipment.",
        words: ["stadium", "hockey", "badminton", "referee", "umpire", "football", "discus", "goalkeeper", "baseball", "cricket", "tennis", "racquet", "golfer", "boxer", "javelin", "track", "arena", "archery", "medal", "trophy"]
    },
    {
        title: "Music",
        picture: "music",
        description: "Look for anything related to music such as instruments or people.",
        words: ["choir", "chorus", "harmonica", "orchestra", "trumpet", "trombone", "piano", "melody", "ballard", "clarinet", "accordion", "flute", "guitar", "cello", "violin", "fiddle", "conductor", "rhythm", "zither", "xylophone"]
    }

];


You'll of course, want to add the following images as well.
birds.png

food.png

music.png



Here's what you get... a random theme with every refresh!










This part of the web tutorial is pretty short, but that's because we did so much great work earlier on. Thanks for joining me today and I hope to see you again soon.

Here's hoping you always find what you're looking for!
T___T

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.

Wednesday 11 November 2020

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

In the previous part, we did quite a fair bit. For today's part, it's a smaller but just as vital piece of the puzzle. We will be hiding the words within the grid. There's quite a bit of logic that goes into this, so bear with me!

We'll start off with something simple. Just before displaying the theme, call the assignWords() method.
begin: function ()
{
    this.themeIndex = this.generateRandomNo(themes.length);
    this.assignWords();
    this.displayTheme();
    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;");
},


Create the method. Grab themeIndex, and set words to an empty array. Declare variable possibleWordList and set it to the words array of the element in themes pointed to by themeIndex. There's only one possible value right now, so it should always return the list of sea creature names.
begin: function ()
{
    this.themeIndex = this.generateRandomNo(themes.length);
    this.assignWords();
    this.displayTheme();
    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;");
},
assignWords: function()
{
    themeIndex = this.themeIndex;
    this.words = [];

    var possibleWordList = themes[themeIndex].words;
},    
            
decrementCounter: function ()
{


And while words has less elements than maxWords...
assignWords: function()
{
    themeIndex = this.themeIndex;
    this.words = [];

    var possibleWordList = themes[themeIndex].words;

    while (this.words.length < this.maxWords)
    {

    }

},


Grab a random element from possibleWordList.
assignWords: function()
{
    themeIndex = this.themeIndex;
    this.words = [];

    var possibleWordList = themes[themeIndex].words;

    while (this.words.length < this.maxWords)
    {
        var assignedWord = possibleWordList[this.generateRandomNo(possibleWordList.length)];
    }
},


Push that word into words, encapsulating it into an object, along with a property, assigned, which is false. Then remove that word from possibleWordList using the filter() method.

What we've basically done is - create a list of all possible words, grab words randomly from the list to populate the words array with, deleting that word from the list of possible words as we do. So the list gets shorter each time and we don't get repeats.
assignWords: function()
{
    themeIndex = this.themeIndex;
    this.words = [];

    var possibleWordList = themes[themeIndex].words;

    while (this.words.length < this.maxWords)
    {
        var assignedWord = possibleWordList[this.generateRandomNo(possibleWordList.length)];

        this.words.push({word: assignedWord, assigned: false});
        possibleWordList = possibleWordList.filter((x) => { return x != assignedWord; });

    }
},


Now call the populateGrid() method.
begin: function ()
{
    this.themeIndex = this.generateRandomNo(themes.length);
    this.assignWords();
    this.displayTheme();
    this.populateGrid();
    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;");
},


populateGrid() is a method that takes words from the words array (which we just populated) and places them randomly in the grid. Right now the div styled by wordgrid is totally empty. We should make sure of that by wiping it clean like this.
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
        )
    }
},
populateGrid: function ()
{
    $(".wordgrid").html("");
},

generateRandomNo: function (max)
{
    return Math.floor((Math.random() * (max)));
},


And then set squares to an empty array.
populateGrid: function ()
{
    $(".wordgrid").html("");
    this.squares = [];
},


We want to make a grid of squares, specifically, maxSize squares by maxSize squares. So what we do is iterate from 0 to maxSize, and make maxSize empty elements in squares.
populateGrid: function ()
{
    $(".wordgrid").html("");
    this.squares = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        this.squares[x] = [];    
    }
},


Then we again iterate from 0 to maxSize, and, using the push() method, set each array in squares to be an array of objects. Each object will have these properties as shown. I'll explain what they're for later on.
populateGrid: function ()
{
    $(".wordgrid").html("");
    this.squares = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        this.squares[x] = [];

        for (var y = 0; y < this.maxSize; y++)
        {
            this.squares[x].push
            (
                {
                    letter: "",
                    selected: false,
                    correct: false,
                    dir: undefined,
                    intersected: false
                }
            );
        } 
   
    }
},


And for each array object, we create a div, and append some useful data in it. We also style it using the CSS class letter.
populateGrid: function ()
{
    $(".wordgrid").html("");
    this.squares = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        this.squares[x] = [];

        for (var y = 0; y < this.maxSize; y++)
        {
            this.squares[x].push
            (
                {
                    letter: "",
                    selected: false,
                    correct: false,
                    dir: undefined,
                    intersected: false
                }
            );

            var square = $("<div><div>");
            square.attr("data-x", x);
            square.attr("data-y", y);
            square.addClass("letter");

        }    
    }
},


And then we set a click event, which, for now, will do absolutely nothing.
populateGrid: function ()
{
    $(".wordgrid").html("");
    this.squares = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        this.squares[x] = [];

        for (var y = 0; y < this.maxSize; y++)
        {
            this.squares[x].push
            (
                {
                    letter: "",
                    selected: false,
                    correct: false,
                    dir: undefined,
                    intersected: false
                }
            );

            var square = $("<div><div>");
            square.attr("data-x", x);
            square.attr("data-y", y);
            square.addClass("letter");
            square.click
            (
                (e) =>
                {

                }
            );

        }    
    }
},


After all that, we then append the element to the div.
populateGrid: function ()
{
    $(".wordgrid").html("");
    this.squares = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        this.squares[x] = [];

        for (var y = 0; y < this.maxSize; y++)
        {
            this.squares[x].push
            (
                {
                    letter: "",
                    selected: false,
                    correct: false,
                    dir: undefined,
                    intersected: false
                }
            );

            var square = $("<div><div>");
            square.attr("data-x", x);
            square.attr("data-y", y);
            square.addClass("letter");
            square.click
            (
                (e) =>
                {

                }
            );

            $(".wordgrid").append(square);
        }    
    }
},


This is the letter CSS class. Width and height have been set to 4%, with some margins. We will float this left. These are the only things necessary for the layout to work; the rest of the properties I'll leave up to your personal taste.
.score .meter .fill
{
    width: 100%;
    height: 100%;
    color: rgba(255, 255, 255, 1);
    text-align: right;
    font-weight: bold;
}

.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%;
}


We have a grid!



Now let's start placing the letters. Declare unassigned, an empty array, and i.
populateGrid: function ()
{
    $(".wordgrid").html("");
    this.squares = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        this.squares[x] = [];

        for (var y = 0; y < this.maxSize; y++)
        {
            this.squares[x].push
            (
                {
                    letter: "",
                    selected: false,
                    correct: false,
                    dir: undefined,
                    intersected: false
                }
            );

            var square = $("<div><div>");
            square.attr("data-x", x);
            square.attr("data-y", y);
            square.addClass("letter");
            square.click
            (
                (e) =>
                {

                }
            );

            $(".wordgrid").append(square);
        }    
    }

    var unassigned = [];
    var i;    

},


The next operation will be carried out, and repeated as long as there are elements in unassigned.
var unassigned = [];
var i;
do
{

}
while (unassigned.length > 0);
    


The method populateGridWithWords() will be called, and after that, we set unassigned to the filtered words array, getting all the elements where assigned is false. Remember the assigned property in words we defaulted to false earlier?

This ensures that all the words in words are assigned before the script can proceed.
var unassigned = [];
var i;
do
{
    this.populateGridWithWords();

    unassigned = this.words.filter((x) => {return !x.assigned;});

}
while (unassigned.length > 0);


And of course, we'll want to create populateGridWithWords(). We start off the method by running a nested For loop to "clean" the data. Basically reset both the squares array and the grid to an initial state. This is important because if this method is being run, not all words have been assigned and we want to start the operation over.
populateGrid: function ()
{
    $(".wordgrid").html("");
    this.squares = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        this.squares[x] = [];

        for (var y = 0; y < this.maxSize; y++)
        {
            this.squares[x].push
            (
                {
                    letter: "",
                    selected: false,
                    correct: false,
                    dir: undefined,
                    intersected: false
                }
            );

            var square = $("<div><div>");
            square.attr("data-x", x);
            square.attr("data-y", y);
            square.addClass("letter");
            square.click
            (
                (e) =>
                {

                }
            );

            $(".wordgrid").append(square);
        }    
    }

    var unassigned = [];
    var i;
    do
    {
        this.populateGridWithWords();

        unassigned = this.words.filter((x) => {return !x.assigned;});
    }
    while (unassigned.length > 0);    
},
populateGridWithWords: function()
{
    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            this.squares[x][y].letter = "";
            this.squares[x][y].correct = false;
            this.squares[x][y].dir = undefined;
            this.squares[x][y].intersected = false;
            $(".letter[data-x=" + x + "][data-y=" + y + "]").html("");
        }    
    }
},

generateRandomNo: function (max)
{
    return Math.floor((Math.random() * (max)));
},


Now iterate through the words array and set all assigned properties to false. Declare variable word, and set it to the current element's word property. Also declare possibleSlots, an empty array.
populateGridWithWords: function()
{
    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            this.squares[x][y].letter = "";
            this.squares[x][y].correct = false;
            this.squares[x][y].dir = undefined;
            this.squares[x][y].intersected = false;
            $(".letter[data-x=" + x + "][data-y=" + y + "]").html("");
        }    
    }

    for (var i = 0; i < this.words.length; i++)
    {
        this.words[i].assigned = false;

        var word = this.words[i].word;
        var possibleSlots = [];
    }

},


Use a nested For loop to go through the entire grid.
for (var i = 0; i < this.words.length; i++)
{
    this.words[i].assigned = false;

    var word = this.words[i].word;
    var possibleSlots = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
                        
        }    
    }  
                     
}


Here, we use the isWordSpaceAvailable() method, which we will create after this. The x and y coordinates (supplied by the nested For loop), the direction variable dir (0 for horizontal and 1 for vertical) and word are passed in as arguments. If the script finds either a horizontal or vertical space, it pushes an object into possible slots. If there are no elements in possibleSlots after this, the entire operation is a failure and we use the return statement to abort it, and retry.
for (var i = 0; i < this.words.length; i++)
{
    this.words[i].assigned = false;

    var word = this.words[i].word;
    var possibleSlots = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            if (this.isWordSpaceAvailable(x, y, 0, word))
            {
                possibleSlots.push({x: x, y: y, dir: 0});
            }

            if (this.isWordSpaceAvailable(x, y, 1, word))
            {
                possibleSlots.push({x: x, y: y, dir: 1});
            }    

            if (possibleSlots.length == 0) return;  
                         
        }    
    }                        
}


Next, we declare variables slot and slotIndex. slotIndex is initialized as 0. Then possibleSlots_intersected is declared. It's an array of all elements in squares that aren't empty strings, as pointed to by the x and y properties in possibleSlots. possibleSlots here means places in the grid which the word can begin in - the x and y coordinates, and direction.
for (var i = 0; i < this.words.length; i++)
{
    this.words[i].assigned = false;

    var word = this.words[i].word;
    var possibleSlots = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            if (this.isWordSpaceAvailable(x, y, 0, word))
            {
                possibleSlots.push({x: x, y: y, dir: 0});
            }

            if (this.isWordSpaceAvailable(x, y, 1, word))
            {
                possibleSlots.push({x: x, y: y, dir: 1});
            }    

            if (possibleSlots.length == 0) return;                            
        }    
    }    

    var slotIndex = 0;
    var slot;

    var possibleSlots_intersected = possibleSlots.filter((s) => {return this.squares[s.x][s.y].letter != "";});    
            
}


This is because we want the words to possibly cross each other, like so. possibleSlots_intersected will provide a list of possible slots that are open to two words sharing a letter space.



So here, a conditional checks if possibleSlots_intersected is empty. If it is not, then that means there are possible places for intersection. We'll use possibleSlots_intersected and the generateRandomNo() method to derive slot.
for (var i = 0; i < this.words.length; i++)
{
    this.words[i].assigned = false;

    var word = this.words[i].word;
    var possibleSlots = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            if (this.isWordSpaceAvailable(x, y, 0, word))
            {
                possibleSlots.push({x: x, y: y, dir: 0});
            }

            if (this.isWordSpaceAvailable(x, y, 1, word))
            {
                possibleSlots.push({x: x, y: y, dir: 1});
            }    

            if (possibleSlots.length == 0) return;                            
        }    
    }    

    var slotIndex = 0;
    var slot;

    var possibleSlots_intersected = possibleSlots.filter((s) => {return this.squares[s.x][s.y].letter != "";});                

    if (possibleSlots_intersected.length == 0)
    {

    }
    else
    {
        slotIndex = this.generateRandomNo(possibleSlots_intersected.length);
        slot = possibleSlots_intersected[slotIndex];
    }    

}


If there are no possible starting slots for intersection, we use possibleSlots instead to derive slot. And then we set that square's intersected property to true. Why, you might ask? It's a wee bit tricky, but trust me, it's necessary. I'll explain soon enough!
for (var i = 0; i < this.words.length; i++)
{
    this.words[i].assigned = false;

    var word = this.words[i].word;
    var possibleSlots = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            if (this.isWordSpaceAvailable(x, y, 0, word))
            {
                possibleSlots.push({x: x, y: y, dir: 0});
            }

            if (this.isWordSpaceAvailable(x, y, 1, word))
            {
                possibleSlots.push({x: x, y: y, dir: 1});
            }    

            if (possibleSlots.length == 0) return;                            
        }    
    }    

    var slotIndex = 0;
    var slot;

    var possibleSlots_intersected = possibleSlots.filter((s) => {return this.squares[s.x][s.y].letter != "";});                

    if (possibleSlots_intersected.length == 0)
    {
        slotIndex = this.generateRandomNo(possibleSlots.length);
        slot = possibleSlots[slotIndex];
        this.squares[slot.x][slot.y].intersected = true;

    }
    else
    {
        slotIndex = this.generateRandomNo(possibleSlots_intersected.length);
        slot = possibleSlots_intersected[slotIndex];
    }
}


Now, use a For loop to iterate through every letter in the word. Declare letters as an array of all the elements in the word, using the split() method. Set a conditional for the dir property, and then set the assigned property to true.
for (var i = 0; i < this.words.length; i++)
{
    this.words[i].assigned = false;

    var word = this.words[i].word;
    var possibleSlots = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            if (this.isWordSpaceAvailable(x, y, 0, word))
            {
                possibleSlots.push({x: x, y: y, dir: 0});
            }

            if (this.isWordSpaceAvailable(x, y, 1, word))
            {
                possibleSlots.push({x: x, y: y, dir: 1});
            }    

            if (possibleSlots.length == 0) return;                            
        }    
    }    

    var slotIndex = 0;
    var slot;

    var possibleSlots_intersected = possibleSlots.filter((s) => {return this.squares[s.x][s.y].letter != "";});                

    if (possibleSlots_intersected.length == 0)
    {
        slotIndex = this.generateRandomNo(possibleSlots.length);
        slot = possibleSlots[slotIndex];
        this.squares[slot.x][slot.y].intersected = true;
    }
    else
    {
        slotIndex = this.generateRandomNo(possibleSlots_intersected.length);
        slot = possibleSlots_intersected[slotIndex];
    }

    for (var j = 0; j < word.length; j++)
    {
        var letters = word.split("");

        if (slot.dir == 0)
        {

        }

        if (slot.dir == 1)
        {

        }

        this.words[i].assigned = true;
    }    

}


What we're doing here is populating the squares array with the letters from the words. dir is important here; again, I'll explain why later. At the end of it, we'll also populate the grid, using jQuery, with the letter and the x and y coordinates.
for (var i = 0; i < this.words.length; i++)
{
    this.words[i].assigned = false;

    var word = this.words[i].word;
    var possibleSlots = [];

    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            if (this.isWordSpaceAvailable(x, y, 0, word))
            {
                possibleSlots.push({x: x, y: y, dir: 0});
            }

            if (this.isWordSpaceAvailable(x, y, 1, word))
            {
                possibleSlots.push({x: x, y: y, dir: 1});
            }    

            if (possibleSlots.length == 0) return;                            
        }    
    }    

    var slotIndex = 0;
    var slot;

    var possibleSlots_intersected = possibleSlots.filter((s) => {return this.squares[s.x][s.y].letter != "";});                

    if (possibleSlots_intersected.length == 0)
    {
        slotIndex = this.generateRandomNo(possibleSlots.length);
        slot = possibleSlots[slotIndex];
        this.squares[slot.x][slot.y].intersected = true;
    }
    else
    {
        slotIndex = this.generateRandomNo(possibleSlots_intersected.length);
        slot = possibleSlots_intersected[slotIndex];
    }

    for (var j = 0; j < word.length; j++)
    {
        var letters = word.split("");

        if (slot.dir == 0)
        {
            this.squares[slot.x + j][slot.y].letter = letters[j];
            this.squares[slot.x + j][slot.y].correct = true;
            this.squares[slot.x + j][slot.y].dir = slot.dir;
            $(".letter[data-x=" + (slot.x + j) + "][data-y=" + (slot.y) + "]").html(letters[j]);

        }

        if (slot.dir == 1)
        {
            this.squares[slot.x][slot.y + j].letter = letters[j];
            this.squares[slot.x][slot.y + j].correct = true;
            this.squares[slot.x][slot.y + j].dir = slot.dir;
            $(".letter[data-x=" + (slot.x) + "][data-y=" + (slot.y + j) + "]").html(letters[j]);

        }

        this.words[i].assigned = true;
    }    
}


And finally, we create the isWordSpaceAvailable() method to help determine if a given square is a suitable place to begin a word. Obviously, there are certain conditions to look out for. If dir is horizontal and the word is so long that starting the word near the end of the grid would cause the word to overrun the set boundaries, then obviously we don't want that. Ditto for vertical.

We begin by declaring letters as an array of all the letters in the parameter word.
populateGridWithWords: function()
{
    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            this.squares[x][y].letter = "";
            this.squares[x][y].correct = false;
            this.squares[x][y].dir = undefined;
            this.squares[x][y].intersected = false;
            $(".letter[data-x=" + x + "][data-y=" + y + "]").html("");
        }    
    }

    for (var i = 0; i < this.words.length; i++)
    {
        this.words[i].assigned = false;

        var word = this.words[i].word;
        var possibleSlots = [];

        for (var x = 0; x < this.maxSize; x++)
        {
            for (var y = 0; y < this.maxSize; y++)
            {
                if (this.isWordSpaceAvailable(x, y, 0, word))
                {
                    possibleSlots.push({x: x, y: y, dir: 0});
                }

                if (this.isWordSpaceAvailable(x, y, 1, word))
                {
                    possibleSlots.push({x: x, y: y, dir: 1});
                }    

                if (possibleSlots.length == 0) return;                            
            }    
        }    

        var slotIndex = 0;
        var slot;

        var possibleSlots_intersected = possibleSlots.filter((s) => {return this.squares[s.x][s.y].letter != "";});

        if (possibleSlots_intersected.length == 0)
        {
            slotIndex = this.generateRandomNo(possibleSlots.length);
            slot = possibleSlots[slotIndex];
            this.squares[slot.x][slot.y].intersected = true;
        }
        else
        {
            slotIndex = this.generateRandomNo(possibleSlots_intersected.length);
            slot = possibleSlots_intersected[slotIndex];
        }

        for (var j = 0; j < word.length; j++)
        {
            var letters = word.split("");

            if (slot.dir == 0)
            {
                this.squares[slot.x + j][slot.y].letter = letters[j];
                this.squares[slot.x + j][slot.y].correct = true;
                this.squares[slot.x + j][slot.y].dir = slot.dir;
                $(".letter[data-x=" + (slot.x + j) + "][data-y=" + (slot.y) + "]").html(letters[j]);
            }

            if (slot.dir == 1)
            {
                this.squares[slot.x][slot.y + j].letter = letters[j];
                this.squares[slot.x][slot.y + j].correct = true;
                this.squares[slot.x][slot.y + j].dir = slot.dir;
                $(".letter[data-x=" + (slot.x) + "][data-y=" + (slot.y + j) + "]").html(letters[j]);
            }

            this.words[i].assigned = true;
        }                    
    }
},
isWordSpaceAvailable: function (x, y, dir, word)
{
    var letters = word.split("");
},

generateRandomNo: function (max)
{
    return Math.floor((Math.random() * (max)));
},


We prepare conditional blocks for dir. In both cases, we return true at the end of the block.
isWordSpaceAvailable: function (x, y, dir, word)
{
    var letters = word.split("");

    if (dir == 0)
    {    
        return true;
    }

    if (dir == 1)
    {
        return true;
    }  
 
},


For a horizontal case, if the length of the word plus the x coordinate would overrun maxSize, we return false.
isWordSpaceAvailable: function (x, y, dir, word)
{
    var letters = word.split("");

    if (dir == 0)
    {    
        if ((x + letters.length) >= this.maxSize) return false;

        return true;
    }

    if (dir == 1)
    {
        return true;
    }    
},


Then we go through all the letters. If the direction has already been set and is the same as the current direction, we return false because we don't want the words to overlap by more than one character - unlikely, but entirely possible depending on the word selection.
isWordSpaceAvailable: function (x, y, dir, word)
{
    var letters = word.split("");

    if (dir == 0)
    {    
        if ((x + letters.length) >= this.maxSize) return false;

        for (var i = 0; i < letters.length; i++)
        {
            if (this.squares[x + i][y].dir == dir) return false;
        }


        return true;
    }

    if (dir == 1)
    {
        return true;
    }    
},


Like in this case...



If the intersected property is true, we also return false. This is so that the word does not intersect an already intersected letter.
isWordSpaceAvailable: function (x, y, dir, word)
{
    var letters = word.split("");

    if (dir == 0)
    {    
        if ((x + letters.length) >= this.maxSize) return false;

        for (var i = 0; i < letters.length; i++)
        {
            if (this.squares[x + i][y].dir == dir) return false;
            if (this.squares[x + i][y].intersected) return false;
        }

        return true;
    }

    if (dir == 1)
    {
        return true;
    }    
},


Basically, I'm trying to prevent a case like this...



And if the space is already occupied by a letter and that letter is not the same as the current letter, we also return false.
isWordSpaceAvailable: function (x, y, dir, word)
{
    var letters = word.split("");

    if (dir == 0)
    {    
        if ((x + letters.length) >= this.maxSize) return false;

        for (var i = 0; i < letters.length; i++)
        {
            if (this.squares[x + i][y].dir == dir) return false;
            if (this.squares[x + i][y].intersected) return false;

            if (this.squares[x + i][y].letter != "" && this.squares[x + i][y].letter != letters[i]) return false;
        }

        return true;
    }

    if (dir == 1)
    {
        return true;
    }    
},


Now repeat this for a vertical direction.
isWordSpaceAvailable: function (x, y, dir, word)
{
    var letters = word.split("");

    if (dir == 0)
    {    
        if ((x + letters.length) >= this.maxSize) return false;

        for (var i = 0; i < letters.length; i++)
        {
            if (this.squares[x + i][y].dir == dir) return false;
            if (this.squares[x + i][y].intersected) return false;

            if (this.squares[x + i][y].letter != "" && this.squares[x + i][y].letter != letters[i]) return false;
        }

        return true;
    }

    if (dir == 1)
    {
        if ((y + letters.length) >= this.maxSize) return false;

        for (var i = 0; i < letters.length; i++)
        {
            if (this.squares[x][y + i].dir == dir) return false;
            if (this.squares[x][y + i].intersected) return false;

            if (this.squares[x][y + i].letter != "" && this.squares[x][y + i].letter != letters[i]) return false;
        }

        return true;

    }    
},


There you go.



Phew! Now that was a lot to unpack. But we're far from done, folks...

Next

Handling clicks, correct and incorrect input.