Wednesday 27 April 2022

Using Trello outside of the professional workplace

As Project Management tools go, Trello by Atlassian is probably one of the best-known ones out there. It is used by both tech practitioners as well as business people. Trello is an online version of the Kanban Board popularized by the Japanese. And for the entirety of 2021, I have been using it to manage my non-professional projects; i.e, my personal life.

I will show you how soon, but first things first...

What is a Kanban Board?

This link can probably explain it better, but here goes: the Kanban Board is a visualization that represents the progress of a task, or a series of tasks. These tasks are placed on the board at positions that denote the stage at which the progress of that task has arrived at. For example - Backlog, In Progress, Completed.

A typical Kanban Board.

The tasks are then moved when progress takes place, until the tasks are completed. The overall effect is a visual of where each task is at, at the current time.

Now that this is hopefully clear, let us examine how the use of this tool can be used in the personal life of a tech professional.

What does Trello manage for me?

What I have used Trello to manage all the fun little code projects I work on, the blogposts that make it onto this blog, some crafts projects and learning assignments.

Chores managed by Trello.

Also, I use Trello to schedule my weekend household chores. Dental. Banking appointments. All that mundane stuff.

Breakdowns into sub-tasks

Checklists can be used in each card to break the task down into smaller chunks. When items in the checklist are checked, there is an indication on the card reflecting how much of the task has been completed. This is important because while the overall status of a task remains binary (i.e, completed or not completed), there is a huge difference between 10% of a task having been completed, and, say, 90%.

Checklists


Also, breaking a task down makes it more manageable. I get to tackle bite-sized chunks instead of an entire otherwise gargantuan task. Take for example, a task like "Learn Japanese". Learning an entirely new language is daunting in itself. But if we broke it down into multiple checklists for reading, writing and speaking, with each checklist further broken down into different sub-tasks, it now looks doable. And these tasks are segregated into logical groups, which lets me see which parts of Japanese I am progressing in more - reading, writing or speaking. What I may want to concentrate on more - vocabulary or grammar. It provides significantly more insight into the task than simply "Learn Japanese".

Daily tasks

Daily tasks are a list of things I want to work on or get done on any given day. They could be an existing card on the board that I need to do work on. They could be some entirely new items that needs to be done on that day, like laundry or grocery shopping.

Miscellaneous tasks


In the case of the latter, those tasks take priority because they have to be finished on the day itself. Therefore I would recommend that there is a limited number of these such items on any given day.

In the case of the former, it is a little more nuanced. The objective is not to finish the task, but rather do work on it. So if I do enough work for one or more items on the checklist to be checked off, it counts. How much work I do on that task does not matter; only that I spend time on it. If I spend time on that task but do not actually manage to check off any items, then obviously I did not break down that task thoroughly enough. Therefore, if I follow my own rules, this forces me to at least do one thing on the checklist. If I do this consistently, eventually, the task gets completed. This serves to enforce discipline.

Overall benefits

I've explained how Trello helps in organization of my personal life and all my required tasks. That is an undeniable benefit, especially since I have to break down tasks to fit into the Kanban Board system, and group these sub-tasks logically. This also means that I get real practice using a system like Trello.

Task management.

As mentioned, Trello (or other systems like it) are in use professionally for task management. Being familiar with something like that lends me a professional edge.

Not only does Trello play a role in my professional life as a software developer, it also plays a role in my personal life. In the larger picture, Trello has kept me pretty much on track.

Completed,
T___T

Wednesday 20 April 2022

Software Review: Power BI

Forays into Data Visualization as an integral part of Data Analytics, have brought me in contact with several Data Visualization tools. One of these is Microsoft's offering, PowerBI. The "BI" part of the name, of course, stands for Business Intelligence, which is one of the use cases for Data Analytics.


If you have a need for ETL that will ultimately factor into your Business Intelligence, this is what PowerBI is for. Of course, PowerBI is not just about Business Intelligence. Any use case you may have involving transformation and visualization of data, will be handled by PowerBI.

The Premise

PowerBI is a desktop (or cloud-based, depending on your package) application that takes a data source or several, and combines them into a slick narrative, cumulating in charts, dashboards and storyboards.


The Aesthetics

Power BI follows a similar aesthetic to most Microsoft products. This means that if you're familiar with Access, SQL Server or Excel, you will feel pretty much at home right off the bat, whether it's managing dataset relationships or cleaning data.


Outside of data visualization colors, white and grey are predominant with button mostly in yellow. There's the occasional flash of green.

The Experience

Honestly, I found Power BI intimidating. Just for the simple task of importing datasets, there was a whole host of options to choose from. Further into the actual cleaning and visualization of data, each task had a blinding array of configuration options.


Thankfully, Power BI manages to stick to a well-established workflow - import data, clean and merge, create analytics, visualize.

The Interface

While the interface can get a little crowded, much of it is safely ignored with little consequence. For the most part, controls are straightforward and easy to find especially if you have had prior experience with Microsoft products.

Of special mention is the relationship relation interface. For such a busy looking display, the process of merging various datasets was relatively direct. And it was not clumsy.






What I liked

The meat of the software, as far as I'm concerned, is in the visualization. On that score, I wasn't disappointed. There are tons of chart types and dashboard options, with each chart highly customizable.





Simple things are easy enough to accomplish if you don't get distracted by all the other interface elements.

Statistical information about the data is available right off the bat when you view it, with more options available if you want it.


I've mentioned this before but it's great that the UI is mostly consistent with other Microsoft products.

What I didn't

Power BI Desktop is free. The costs of Pro or Premium seem a little high, though perhaps this can be justified with scaled-up use cases.

I found the going slow at times. This may be improved with the Pro or Premium versions, and may just be a result of me running Power BI on my ridiculously old Windows machine. Use on a Macbook was a no-go last I checked.

The screen can sometimes get a little crowded with the sheer amount of controls provided. Half of which I will probably never use.

Conclusion

PowerBI isn't what I would call a slick little package. It has a whole host of functions and sometimes all that can be overwhelming. Still, it's good enough for most cases even if the functions I really need are sometimes annoyingly elusive, hidden amongst a ton of related functionality.

I would absolutely recommend PowerBI for your Business Intelligence use cases. And if you can afford it, PowerBI Premium or PowerBI Pro.

My Rating

7 / 10

Powerful stuff!
T___T

Thursday 14 April 2022

Web Tutorial: Easter Egg Hunt (Part 4/4)

Hey, beautiful people! This is the final part, and we will soon be good to go!

We want the game to end when the user has collected 30 eggs, or if the number of minutes left has reached 0. In the showChoices() method, we will do just that. If either condition is met, we do not display any of the choices that would normally show, and instead display the choice to a section that either tells the users that they have run out of time, or congratulates them for winning the game.

We begin by declaring variable gameOver, and set it to false as a default.

index.html
"showChoices" : function(sectionName, links)
{
    var gameOver = false;
    $(".choices").html("");


If the timeLeft property is true and the current section is not "timeout", we set gameOver to true.
index.html
var gameOver = false;
$(".choices").html("");

if (game.timeLeft == 0 && sectionName != "timeout")
{
    gameOver = true;
}


Then we create a new button with some explanatory text, and append it to the choices div the way we would for any other choice. We then use the click event to point the user to the section "timeout".

index.html
if (game.timeLeft == 0 && sectionName != "timeout")
{
    gameOver = true;

    var div = $("<div></div>");
    var button = $("<button>Go</button>");
    div.html("You have run out of time!");
    div.append(button);
    $(".choices").append(div);

    button.click(()=> {
        game.gotoSection("timeout", 0, null);
    });  
 
}


Duplicate that If block.

index.html
if (game.timeLeft == 0 && sectionName != "timeout")
{
    gameOver = true;

    var div = $("<div></div>");
    var button = $("<button>Go</button>");
    div.html("You have run out of time!");
    div.append(button);
    $(".choices").append(div);

    button.click(()=> {
        game.gotoSection("timeout", 0, null);
    });    
}

if (game.timeLeft == 0 && sectionName != "timeout")
{
    gameOver = true;

    var div = $("<div></div>");
    var button = $("<button>Go</button>");
    div.html("You have run out of time!");
    div.append(button);
    $(".choices").append(div);

    button.click(()=> {
        game.gotoSection("timeout", 0, null);
    });    
}


Now make the following changes. You should see what we're trying to do here. The clause has changed to compare the value of the eggs property, and the section name is "victory". And, of course, the choice text is different.

index.html
if (game.timeLeft == 0 && sectionName != "timeout")
{
    gameOver = true;

    var div = $("<div></div>");
    var button = $("<button>Go</button>");
    div.html("You have run out of time!");
    div.append(button);
    $(".choices").append(div);

    button.click(()=> {
        game.gotoSection("timeout", 0, null);
    });    
}

if (game.eggs >= 30 && sectionName != "victory")
{
    gameOver = true;

    var div = $("<div></div>");
    var button = $("<button>Go</button>");
    div.html("Congrats! You have completed the Easter Egg Hunt!");
    div.append(button);
    $(".choices").append(div);

    button.click(()=> {
        game.gotoSection("victory", 0, null);
    });    
}


We follow up by making sure the other choices do not show up if gameOver is true.

index.html
if (!gameOver)
{

    for (let i = 0; i < links.length; i++)
    {
        var div = $("<div></div>");
        var button = $("<button>Go</button>");
        div.html(links[i].text);
        div.append(button);
        $(".choices").append(div);

        button.click(()=> {
            game.gotoSection(links[i].section, links[i].time, links[i].eggs);
        });                                
    }
}


In order to test this, we should do this at "test". This ensures that if the user chooses to "Get the Mercedes", time will go to 0 (by deducting 120) and eggs will increase by 30. Thus both conditions are met. In reality, your game should make this scenario extremely unlikely, if not impossible.

sections.js
"test" :
{
    "content" : "<p>(test)</p>",
    "flag" : false,
    "visitedChecks" : [
        { "section": "testGetMercedes", "content" : "<p>You have the Mercedes.</p>", "choices": null, "contentElse" : "<p>You see a Mercedes.</p>", "choicesElse" : { "time" : -120, "eggs" : 30, "section" : "testGetMercedes", "text" : "Get the Mercedes"} },
    ],
    "messages" : [],
    "choices" : []    
},


And there we are. The two choices are now there, and the choice that would have been there, "Go back to test", is no longer there. The "Restart" choice is still there, because we didn't (and aren't going to) mess with it.




Now add these sections, "timeout" and "victory".

sections.js
"begin" :
{
    "content" : "<p>Easter has come. The sky is bright and cheery. You are on the porch of the house you live in, facing the Easter Bunny. The Easter Bunny wiggles his little nose and tells you, &quot;<b>Happy Easter! I have hidden several Easter eggs all around the garden, outside of the house. Your mission is to find 30 eggs within 120 minutes. Good hunting!</b>&quot; With that, he hops away.</p><p><img src='img_easterbunny.jpg'></p><p>You are now seated on the steps, and the hunt is under way.</p>",
    "flag" : false,
    "visitedChecks" : null,
    "messages" : [],
    "choices" :
    [
        { "time" : -1, "eggs" : null, "section" : "test", "text" : "Begin the hunt!"}
    ]
},
"timeout" :
{
    "content" : "<p>The Easter Bunny appears. &quot;<b>Well, looks like your time is up! Better luck next Easter!</b>&quot;</p><p><img src='img_easterbunny.jpg'></p>",
    "flag" : false,
    "visitedChecks" : null,
    "messages" : [],
    "choices" : []
},
"victory" :
{
    "content" : "<p>The Easter Bunny appears, looking impressed. &quot;<b>Well, looks like you managed to complete the Easter Egg Hunt! That is great work!</b>&quot;</p><p><img src='img_easterbunny.jpg'></p>",
    "flag" : false,
    "visitedChecks" : null,
    "messages" : [],
    "choices" : []
},
    
"test" :
{
    "content" : "<p>(test)</p>",
    "flag" : false,
    "visitedChecks" : [
        { "section": "testGetMercedes", "content" : "<p>You have the Mercedes.</p>", "choices": null, "contentElse" : "<p>You see a Mercedes.</p>", "choicesElse" : { "time" : -120, "eggs" : 30, "section" : "testGetMercedes", "text" : "Get the Mercedes"} },
    ],
    "messages" : [],
    "choices" : []    
},


Now try clicking through!







Final touches

This isn't the end of it, of course. All I have given you are the building blocks to create your own Easter Egg Hunt story. The entire listing of sections.js is at this repository.

And if you are using that, you should definitely make these changes to your showContent() method.

index.html
var invStr = "Eggs x " + game.eggs + "<br />";
//if (game.hasVisited("testGetMercedes")) invStr += "Mercedes x 1<br />";
if (game.hasVisited("porch_kuching")) invStr += "Cat x 1<br />";
if (game.hasVisited("porch_shedkey")) invStr += "Shed key x 1<br />";
if (game.hasVisited("shed_boltcutters")) invStr += "Bolt cutters x 1<br />";
if (game.hasVisited("neighbor_ladder")) invStr += "Ladder x 1<br />";

$(".inventory").html(invStr);


That's all!

I hope you enjoyed this web tutorial. And I hope you enjoy your Easter!

Your Easter Bunny,
T___T

Monday 11 April 2022

Web Tutorial: Easter Egg Hunt (Part 3/4)

Now that we have a content and choices display, we will do inventory display. And this is only possible via checking which sections have been visited. To do that, we need a new method, hasVisited(), which will read the visited property. This is relatively straightforward. The method has a parameter, sectionName. We return true or false depending on whether sectionName appears in the visited array.

index.html
    "modifyEggs" : function(qty)
    {
        if (qty != null)
        {
            if (qty == 0)
            {
                game.eggs = 0;
            }
            else
            {
                game.eggs += qty;

                if (game.eggs < 0) game.eggs = 0;    
            }
        }
    },
    "hasVisited" : function(sectionName) {
        return (game.visited.indexOf(sectionName) != -1);
    }

};


But where do we modify the visited property? Ha! Glad you asked!

In the gotoSection() method, add this conditional check. If the flag property of the current section is true, add sectionName to the visited array. Now, this case is not going to come up simply because the only section we have added in sections.js so far, has the flag property set to false.

index.html
game.modifyTime(mins);
game.modifyEggs(eggs);

if (section.flag) game.visited.push(sectionName);

var content = section.content;
var choices = JSON.parse(JSON.stringify(section.choices));


So in sections.js, we are going to add two more elements. They will be sections named "test" and "testGetMercedes". I've added some test content. The flag property for section "testGetMercedes" is set to true.

sections.js
"begin" :
{
    "content" : "<p>Easter has come. The sky is bright and cheery. You are on the porch of the house you live in, facing the Easter Bunny. The Easter Bunny wiggles his little nose and tells you, &quot;<b>Happy Easter! I have hidden several Easter eggs all around the garden, outside of the house. Your mission is to find 30 eggs within 120 minutes. Good hunting!</b>&quot; With that, he hops away.</p><p><img src='img_easterbunny.jpg'></p><p>You are now seated on the steps, and the hunt is under way.</p>",
    "flag" : false,
    "visitedChecks" : null,
    "messages" : [],
    "choices" :
    [
        { "time" : -1, "eggs" : null, "section" : "test", "text" : "Begin the hunt!"}
    ]
},    
"test" :
{
    "content" : "<p>(test) You see a Mercedes.</p>",
    "flag" : false,
    "visitedChecks" : null,
    "messages" : [],
    "choices" : []
},
"testGetMercedes" :
{
    "content" : "<p>(test) You grab the Mercedes.</p>",
    "flag" : true,
    "visitedChecks" : null,
    "messages" : [],
    "choices" : []
},


For these two sections, we now add an entry into their choices arrays. For "test", we add a choice that deducts 1 minute, does nothing to eggs and goes to "testGetMercedes". For "testGetMercedes", we add a choice that deducts 1 minute, does nothing to eggs and goes to "test".

sections.js
"test" :
{
    "content" : "<p>(test) You see a Mercedes.</p>",
    "flag" : false,
    "visitedChecks" : null,
    "messages" : [],
    "choices" : [
        { "time" : -1, "eggs" : null, "section" : "testGetMercedes", "text" : "Get the Mercedes"},
    ]
},
"testGetMercedes" :
{
    "content" : "<p>(test) You grab the Mercedes.</p>",
    "flag" : true,
    "visitedChecks" : null,
    "messages" : [],
    "choices" : [
        { "time" : -1, "eggs" : null, "section" : "test", "text" : "Go back to test"},
    ]
},


Clicking on "Get the Mercedes" should bring you to the second screenshot. And clicking on "Go back to test" should bring you back to the first!





But the Mercedes is still not in your inventory! No sweat, you just need a line in showContent(). In there, you use the hasVisited() method to check if "testGetMercedes" is in the visited array, and if so, add the info to invStr.

index.html
"showContent" : function(content, messages)
{
    $(".timeleft").html(game.timeLeft + " minutes");

    var invStr = "Eggs x " + game.eggs + "<br />";
    if (game.hasVisited("testGetMercedes")) invStr += "Mercedes x 1<br />";
    $(".inventory").html(invStr);


Now as soon as you click on "Get the Mercedes", you should see "Mercedes x 1" in the sidebar.




More operations involving the hasVisited() method

The hasVisited() method is going to be way more useful than what we have just outlined here. Say, if you don't want the user to get more than one Mercedes (how many of those do you really need anyway, even in an online game?), we want a way to prevent the user from getting to that section again. For that, let's get back to sections.js and modify the section "test".

Firstly, visitedChecks should be an array instead of null. We should also change the content property to just "(test)" because we want to use the text elsewhere.
sections.js
"test" :
{
    "content" : "<p>(test)</p>",
    "flag" : false,
    "visitedChecks" : [],
    "messages" : [],
    "choices" : [
        { "time" : -1, "eggs" : null, "section" : "testGetMercedes", "text" : "Get the Mercedes"},
    ]
},


In visitedChecks, we add one object, which has the following properties.
section - that's the name of the section we're checking. In this case, we're checking if "testGetMercedes" has been visited.
content - the HTML that appears if the section has been visited. The default value is null.
choices - the choices that appear if the section has been visited. The default value is null.
contentElse - the HTML that appears if the section has not been visited. The default value is null.
choicesElse - the choices that appear if the section has not been visited. The default value is null.

sections.js
"test" :
{
    "content" : "<p>(test)</p>",
    "flag" : false,
    "visitedChecks" : [
        { "section": "testGetMercedes", "content" : null, "choices": null, "contentElse" : null, "choicesElse" : null },
    ],
    "messages" : [],
    "choices" : []    
},


So for content, we specify that this HTML will appear if "testGetMercedes" was visited. We don't want any choices to appear for this, so we keep it at null.

sections.js
"test" :
{
    "content" : "<p>(test)</p>",
    "flag" : false,
    "visitedChecks" : [
        { "section": "testGetMercedes", "content" : "<p>You have the Mercedes.</p>", "choices": null, "contentElse" : null, "choicesElse" : null },
    ],
    "messages" : [],
    "choices" : []    
},


Here, for contentElse, we see that "You see a Mercedes" now appears in contentElse, because this HTML only appears if "testGetMercedes" has not been visited.

sections.js
"test" :
{
    "content" : "<p>(test)</p>",
    "flag" : false,
    "visitedChecks" : [
        { "section": "testGetMercedes", "content" : "<p>You have the Mercedes.</p>", "choices": null, "contentElse" : "<p>You see a Mercedes.</p>", "choicesElse" : null },
    ],
    "messages" : [],
    "choices" : []    
},


And this choice only appears if "testGetMercedes" has not been visited. The template is the same as other choices we've created.

sections.js
"test" :
{
    "content" : "<p>(test)</p>",
    "flag" : false,
    "visitedChecks" : [
        { "section": "testGetMercedes", "content" : "<p>You have the Mercedes.</p>", "choices": null, "contentElse" : "<p>You see a Mercedes.</p>", "choicesElse" : { "time" : -1, "eggs" : null, "section" : "testGetMercedes", "text" : "Get the Mercedes"} },
    ],
    "messages" : [],
    "choices" : []    
},


Now, this isn't going to appear just yet. There's more work to be done. In the showContent() method, we declare visitedChecks as a variable and set its value as the visitedChecks property of the current section. And the following code only applies if visitedChecks is not null.

index.html
var content = section.content;
var choices = JSON.parse(JSON.stringify(section.choices));

var visitedChecks = section.visitedChecks;
if (visitedChecks != null)
{
                        
}


game.showContent(content, messages);
game.showChoices(sectionName, choices);


Now since visitedChecks is an array, we want to go through every element in there. For our example, though, we only have one element. In here, we want to check if that section specified by the current element of visitedChecks has been visited, using the hasVisited() method. We'll set up an If-else block for that.

index.html
if (visitedChecks != null)
{
    for (let i = 0; i < visitedChecks.length; i++)
    {
        if (game.hasVisited(visitedChecks[i].section))
        {

        }
        else
        {
            
        }
    }
                        
}

game.showContent(content, messages);
game.showChoices(sectionName, choices);


And in here, if that section has been visited, we next check if the choices property of that element is null. If not, we push that value into the current choices array. We do the same for content, except instead of pushing into it, we just add to it.

index.html
if (visitedChecks != null)
{
    for (let i = 0; i < visitedChecks.length; i++)
    {
        if (game.hasVisited(visitedChecks[i].section))
        {
            if (visitedChecks[i].choices != null) choices.push(visitedChecks[i].choices);
            if (visitedChecks[i].content != null) content += visitedChecks[i].content;

        }
        else
        {
            
        }
    }                        
}

game.showContent(content, messages);
game.showChoices(sectionName, choices);


And here we repeat the process, except for contentElse and choicesElse. Now after this block of code, the choices and content will be run through the showChoices() and showContent() methods.

index.html
if (visitedChecks != null)
{
    for (let i = 0; i < visitedChecks.length; i++)
    {
        if (game.hasVisited(visitedChecks[i].section))
        {
            if (visitedChecks[i].choices != null) choices.push(visitedChecks[i].choices);
            if (visitedChecks[i].content != null) content += visitedChecks[i].content;
        }
        else
        {
            if (visitedChecks[i].choicesElse != null) choices.push(visitedChecks[i].choicesElse);
            if (visitedChecks[i].contentElse != null) content += visitedChecks[i].contentElse;  
         
        }
    }                        
}

game.showContent(content, messages);
game.showChoices(sectionName, choices);


Let's test this! You see "(test)", followed by the HTML and choices that should appear if you have not visited "testGetMercedes".




If you click n "Get the Mercedes", you should see what you got the last time. Now click "Go back to test"...




...and now you should see something completely different! The HTML has changed for the section "test" and there are no choices other than the default "Restart" button.




Next

Good work! With all this done, all that's really left is to work on win/lose scenarios.

Friday 8 April 2022

Web Tutorial: Easter Egg Hunt (Part 2/4)

Welcome back, programmers! In the previous part, we took care of the HTML and CSS for the application. We will now prepare the JavaScript portion.

In the script tag which has remained empty up to now, we add a game object. We then have jQuery's ready() method to execute any functionality we want on page load.

index.html
<script>
    let game = {

    };

    $( document ).ready(function()
    {

    });

</script>


For the game object, we have the following properties.

timeLeft - this is an integer that measures the minutes remaining.
eggs - this is an integer that keeps track of how many eggs have been collected so far.
visited - an array that keeps track of the locations in the story that the user has encountered.

index.html
let game = {
    "timeLeft" : 0,
    "eggs" : 0,
    "visited" : []


That's all for properties. We will now focus on methods. There are a bunch of methods we will be using, but we will be covering just a few for now. The first one is gotoSection(). It has three parameters - sectionName, mins and eggs. This is how it should work: when the method is run, the timeLeft and eggs properties of the game object should be modified according to the value of mins and eggs. And then content and choices will be displayed based on sectionName.

index.html
let game = {
    "timeLeft" : 0,
    "eggs" : 0,
    "visited" : [],
    "gotoSection" : function(sectionName, mins, eggs)
    {

    }

};


We begin this method by making sure that if sectionName is "begin", we reset the visited property to an empty array. We also run modifyTime() and modifyEggs() with mins and eggs passed in as arguments, respectively.

index.html
"gotoSection" : function(sectionName, mins, eggs)
{
    if (sectionName == "begin") game.visited = [];

    game.modifyTime(mins);
    game.modifyEggs(eggs);

}


Let us build these methods.

index.html
"gotoSection" : function(sectionName, mins, eggs)
{
    if (sectionName == "begin") game.visited = [];

    game.modifyTime(mins);
    game.modifyEggs(eggs);
},
"modifyTime" : function(mins)
{

},
"modifyEggs" : function(qty)
{

}


We will work on the modifyTime() method. Now pay close attention because this will be relevant later. Here we will handle two cases. The first case is when the value of mins is greater than 0. In that case, you set the value of the timeLeft property to mins.

index.html
"modifyTime" : function(mins)
{
    if (mins > 0) game.timeLeft = mins;
},


If the value of mins is less than 0, that means we want to deduct from timeLeft. And if the value is less than 0 after the deduction, we set it to 0.

index.html
"modifyTime" : function(mins)
{
    if (mins > 0) game.timeLeft = mins;
    if (mins < 0)
    {
        game.timeLeft += mins;

        if (game.timeLeft < 0) game.timeLeft = 0;
    }

},


Next, we work on the modifyEggs() method. We can pass a null into the method, which will ensure that it does nothing. So to that end, we have an If block that checks that qty is not null.

index.html
"modifyEggs" : function(qty)
{
    if (qty != null)
    {

    }

}


In that block we check if qty is 0. If so, we set the eggs property to 0.
index.html
"modifyEggs" : function(qty)
{
    if (qty != null)
    {
        if (qty == 0)
        {
            game.eggs = 0;
        }
        else
        {
    
        }

    }
}


If it is anything but 0, we increment eggs by that number. This might be a negative number, so if the result is less than 0, we set eggs to 0.

index.html
"modifyEggs" : function(qty)
{
    if (qty != null)
    {
        if (qty == 0)
        {
            game.eggs = 0;
        }
        else
        {
            game.eggs += qty;

            if (game.eggs < 0) game.eggs = 0;  
 
        }
    }
}


Now in the file sections.js, which we included in the last part of this web tutorial, we will add code. The first thing we need is the section object.

sections.js
let sections =
{

};


It is made out of several sub-objects, all with the same properties. We will add only one, for now. Let's call it "begin". It will have the following properties.

content - some HTML. Leave blank for the time being.
flag - to determine if we should record this section as being visited when encountered. false by default.
visitedChecks - more sub-objects that will determine extra content and choices. null by default.
messages - an array of useful messages to display when this section is encountered. Empty array by default.
choices - an array of objects. These are the links to different sections. Empty array by default.

sections.js
let sections =
{
    "begin" :
    {
        "content" : "",
        "flag" : false,
        "visitedChecks" : null,
        "messages" : [],
        "choices" : []
    }

};


For content, let's put in some HTML. Note that double quotes in the content have to be represented by HTML special characters. In this section, we will also have the image below.

img_easterbunny.jpg

The styling has already been done in the last part of this web tutorial.

sections.js
let sections =
{
    "begin" :
    {
        "content" : "<p>Easter has come. The sky is bright and cheery. You are on the porch of the house you live in, facing the Easter Bunny. The Easter Bunny wiggles his little nose and tells you, &quot;<b>Happy Easter! I have hidden several Easter eggs all around the garden, outside of the house. Your mission is to find 30 eggs within 120 minutes. Good hunting!</b>&quot; With that, he hops away.</p><p><img src='img_easterbunny.jpg'></p><p>You are now seated on the steps, and the hunt is under way.</p>",
        "flag" : false,
        "visitedChecks" : null,
        "messages" : [],
        "choices" : []
    }
};


For choices, each object looks something like this. The properties are:
time - the number to set the timeLeft property to, or subtract by.
eggs - the number to set the eggs property to, or add/subtract by.
section - the name of the section to go to when this choice is selected.
text - the text that appears beside the button.

sections.js
let sections =
{
    "begin" :
    {
        "content" : "<p>Easter has come. The sky is bright and cheery. You are on the porch of the house you live in, facing the Easter Bunny. The Easter Bunny wiggles his little nose and tells you, &quot;<b>Happy Easter! I have hidden several Easter eggs all around the garden, outside of the house. Your mission is to find 30 eggs within 120 minutes. Good hunting!</b>&quot; With that, he hops away.</p><p><img src='img_easterbunny.jpg'></p><p>You are now seated on the steps, and the hunt is under way.</p>",
        "flag" : false,
        "visitedChecks" : null,
        "messages" : [],
        "choices" :
        [
            { "time" : -1, "eggs" : null, "section" : "test", "text" : "Begin the hunt!"}
        ]
    }
};


None of that will be apparent now. We need to do some more work first. So let's get back to index.html and work on the gotoSection() method. The next thing we will do is grab the section that sectionName points to, and assign it to the variable section. We then get messages from the messages property of section.

index.html
"gotoSection" : function(sectionName, mins, eggs)
{
    if (sectionName == "begin") game.visited = [];

    var section = sections[sectionName];
    var messages = section.messages;

                    
    game.modifyTime(mins);
    game.modifyEggs(eggs);    
}


We follow up by grabbing content the same way we did for messages. choices as well... though in the case of choices, we also do a stringify() and parse() on the value so as to ensure that we clone the choices object from section.

index.html
"gotoSection" : function(sectionName, mins, eggs)
{
    if (sectionName == "begin") game.visited = [];

    var section = sections[sectionName];
    var messages = section.messages;
                    
    game.modifyTime(mins);
    game.modifyEggs(eggs);
    
    var content = section.content;
    var choices = JSON.parse(JSON.stringify(section.choices)); 
   
}


And then the next thing we do is run the showContent() method and pass content and messages into it as arguments. And we also run the showChoices() method, and pass in sectionName and choices as arguments.

index.html
"gotoSection" : function(sectionName, mins, eggs)
{
    if (sectionName == "begin") game.visited = [];

    var section = sections[sectionName];
    var messages = section.messages;
                    
    game.modifyTime(mins);
    game.modifyEggs(eggs);
    
    var content = section.content;
    var choices = JSON.parse(JSON.stringify(section.choices));    
    
    game.showContent(content, messages);
    game.showChoices(sectionName, choices);

}


Naturally, we next define showContent() and showChoices().

index.html
    game.showContent(content, messages);
    game.showChoices(sectionName, choices);
},
"showChoices" : function(sectionName, links)
{
                
},
"showContent" : function(content, messages)
{

},

"modifyTime" : function(mins)
{
    if (mins > 0) game.timeLeft = mins;
    if (mins < 0)
    {
        game.timeLeft += mins;

        if (game.timeLeft < 0) game.timeLeft = 0;
    }
},


Let us work on showContent() first. It will show (surprise, surprise) content and inventory, along with time left, and any messages that might need to be shown. We begin by putting a string within the div that has the CSS class timeLeft, to sow how many minutes are left.

index.html
"showContent" : function(content, messages)
{
    $(".timeleft").html(game.timeLeft + " minutes");
},


And then we do the same with the div with the CSS class inventory. However, because we will be expanding on this later, we first assign the string to the variable invStr.

index.html
"showContent" : function(content, messages)
{
    $(".timeleft").html(game.timeLeft + " minutes");

    var invStr = "Eggs x " + game.eggs + "<br />";
    $(".inventory").html(invStr);

},


The next thing we do, is populate the div that has the CSS class content, with the value of content.

index.html
"showContent" : function(content, messages)
{
    $(".timeleft").html(game.timeLeft + " minutes");

    var invStr = "Eggs x " + game.eggs + "<br />";
    $(".inventory").html(invStr);

    $(".content").html(content);
},


Next, if messages is not an empty array...

index.html
"showContent" : function(content, messages)
{
    $(".timeleft").html(game.timeLeft + " minutes");

    var invStr = "Eggs x " + game.eggs + "<br />";
    $(".inventory").html(invStr);

    $(".content").html(content);

    if (messages.length > 0)
    {
                        
    }
},


Now we create a new div. It will be the variable div. We also create a variable, messageStr, and set it to an empty string.

index.html
if (messages.length > 0)
{
    var div = $("<div class='message'></div>");
    var messageStr = "";          
             
}


Now we use a For loop to iterate through messages. We will append each string to messageStr, with a break tag.

index.html
if (messages.length > 0)
{
    var div = $("<div class='message'></div>");
    var messageStr = "";

    for (let i = 0; i < messages.length; i++)
    {
        messageStr += (messages[i] + "<br />");
    }  
                     
}


We set div's HTML content by using its html() method and passing in messageStr as an argument. After that, we insert div into the div styled using the CSS class content.

index.html
if (messages.length > 0)
{
    var div = $("<div class='message'></div>");
    var messageStr = "";

    for (let i = 0; i < messages.length; i++)
    {
        messageStr += (messages[i] + "<br />");
    }

    div.html(messageStr);
    $(".content").append(div); 
                       
}


It will not be visible until we call the gotoSection() method. So we call it in here. We pass in "begin", which is the name of the section we created in sections.js. The next argument is 120, which is what we want to set the timeLeft property to. After that, is 0, which is what we want to set the eggs property to.

index.html
$( document ).ready(function()
{
    game.gotoSection("begin", 120, 0);
});


Now you see this! The left column shows time left and eggs. The right column shows the HTML content we had in sections.js. See how the image of the bunny fits in - we handled all that CSS styling in the first part of this tutorial.




Now we add some test content into the messages property of the section we created.

sections.js
"begin" :
{
    "content" : "<p>Easter has come. The sky is bright and cheery. You are on the porch of the house you live in, facing the Easter Bunny. The Easter Bunny wiggles his little nose and tells you, &quot;<b>Happy Easter! I have hidden several Easter eggs all around the garden, outside of the house. Your mission is to find 30 eggs within 120 minutes. Good hunting!</b>&quot; With that, he hops away.</p><p><img src='img_easterbunny.jpg'></p><p>You are now seated on the steps, and the hunt is under way.</p>",
    "flag" : false,
    "visitedChecks" : null,
    "messages" : ['test1', 'test2'],
    "choices" :
    [
        { "time" : -1, "eggs" : null, "section" : "test", "text" : "Begin the hunt!"}
    ]
}


Note that we have to scroll down because the content is too large for the div. But you can see clearly the translucent black div with the red text.




Now revert that messages array to an empty array. We will work on the showChoices() method next. We begin by using the html() method to clear the HTML of the div that is styled using the CSS class choices.

index.html
"showChoices" : function(sectionName, links)
{
    $(".choices").html("");                
},


Then we use a For loop to go through the array links.

index.html
"showChoices" : function(sectionName, links)
{
    $(".choices").html("");

    for (let i = 0; i < links.length; i++)
    {
                                
    }  
             
},


For every element, we create a div and a button with the text "Go", and ensure that div has the text property specified in the current element of the links array. Then we append button to div. And after that, we append div to the div styled using the CSS class choices.

index.html
"showChoices" : function(sectionName, links)
{
    $(".choices").html("");

    for (let i = 0; i < links.length; i++)
    {
        var div = $("<div></div>");
        var button = $("<button>Go</button>");
        div.html(links[i].text);
        div.append(button);
       
$(".choices").append(div);                           
    }                
},


After that, we affix the gotoSection() method into the click event of button. In it, we pass in the appropriate arguments, which are provided by the current element in the links array.

index.html
"showChoices" : function(sectionName, links)
{
    $(".choices").html("");

    for (let i = 0; i < links.length; i++)
    {
        var div = $("<div></div>");
        var button = $("<button>Go</button>");
        div.html(links[i].text);
        div.append(button);
        $(".choices").append(div);

        button.click(()=> {
            game.gotoSection(links[i].section, links[i].time, links[i].eggs);
        });  
                             
    }                
},


The yellow button won't be clickable right now because the section it is pointing to does not exist, but at least the button shows up!




Now let us add one more button, which will appear for all scenarios. It is the restart button.

index.html
"showChoices" : function(sectionName, links)
{
    $(".choices").html("");

    for (let i = 0; i < links.length; i++)
    {
        var div = $("<div></div>");
        var button = $("<button>Go</button>");
        div.html(links[i].text);
        div.append(button);
        $(".choices").append(div);

        button.click(()=> {
            game.gotoSection(links[i].section, links[i].time, links[i].eggs);
        });                                
    }    
    
    var div = $("<div></div>");
    var button = $("<button>Go</button>");
    div.html("Restart");
    div.append(button);
    $(".choices").append(div);

    button.click(()=> {
        game.gotoSection("begin", 120, 0);
    });    

},


There you go. Click that button and you should still see the same thing. The magic is only apparent if you're in another section. No worries, we will handle that in the next part of this tutorial.




Next

We are going to dive right into inventory display!

Tuesday 5 April 2022

Web Tutorial: Easter Egg Hunt (Part 1/4)

Easter approaches, and with it our annual Easter-themed web tutorial! Today, I want to present a fun little thing I did modelling on my childhood experiences reading the Choose Your Own Adventure books. In these books, you read a story and at certain points, choices are presented to the reader, who must then continue the story by turning to the page required. And here, I want to do a web-based version of that.

How it works

There is an array of objects that will be in a separate JavaScript file, and each object has a series of links that will lead to different objects. The choices presented, along with the text for the section the user is currently visiting, will be in different HTML placeholders.

And since this is an Easter Egg Hunt, there will be some kind of inventory-keeping mechanism, along with a timekeeping mechanism. Visiting a section causes a varying amount of time to be lost. If the user runs out of time, the game is lost. Once the user has accumulated a certain number of Easter eggs, the game is won.

Now on to the tutorial!

We begin with some HTML. It will have a link to jQuery. Yes, we're going to use jQuery for this. We will also have a link to the file where the sections are stored, sections.js. For the beginning styling, we make all divs have a thin red border, and we'll have some overall font styling.

index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Easter Egg Hunt</title>

        <style>
            div {outline: 1px solid #FF0000;}

            body
            {
                font-size: 12px;
                font-family: verdana;
            }
        </style>

        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
        <script src="sections.js"></script>
        <script>

        </script>
    </head>

    <body>

    </body>
</html>


We insert one div into the body and style it using the CSS class container. Following up on that, there should be a div inside that, styled using the CSS class top. We can just put in some text. There will be another div, and style it using the CSS class main.

index.html
<body>
    <div class="container">
        <div class="top">
            <p><i>TeochewThunder</i> presents</p>
            <h1>Easter Egg Hunt</h1>
        </div>

        <div class="main">

        </div>
    </div>

</body>


This is the styling for container. We will give it a definite width and height, and a 5 pixel padding. The margin property sets it to the middle of the screen.

index.html
<style>
    div {outline: 0px solid #FF0000;}

    body
    {
        font-size: 12px;
        font-family: verdana;
    }

    .container
    {
        width: 800px;
        height: 600px;
        padding: 5px;
        margin: 10px auto 0 auto;
    }

</style>


top has the same width and a much smaller height. More importantly, we float it left and make the text a translucent yellow. Text is aligned center. The h1 tag in top will also have font settings. For main, we will set width and height, and ensure that there is a top margin of 5 pixels. We will also float this left.

index.html
<style>
    div {outline: 0px solid #FF0000;}

    body
    {
        font-size: 12px;
        font-family: verdana;
    }

    .container
    {
        width: 800px;
        height: 600px;
        padding: 5px;
        margin: 10px auto 0 auto;
    }

    .top
    {
        width: 800px;
        height: 100px;
        float: left;
        color: rgba(255, 255, 0, 0.8);
        text-align: center;
    }

    .top h1
    {
        font-size: 3em;
        font-weight: bold;
    }

    .main
    {
        width: 800px;
        height: 495px;
        margin-top: 5px;
        float: left;
    }

</style>


You should see the general framework in which this takes place. Make sure you actually have a file named sections.js (this can be empty), or this may throw an error.




Now in the div styled using CSS class main, we have two other divs, styled using sidebar and section.

index.html
<div class="main">
    <div class="sidebar">

    </div>

    <div class="section">

    </div>

</div>


Here's the styling for the CSS class sidebar. It has a width and height, and is designed to fit right into the left side of its parent via the float property being set to left. Here, I'm going to set the text color to black.

index.html
.main
{
    width: 800px;
    height: 495px;
    margin-top: 5px;
    float: left;
}

.sidebar
{
    width: 200px;
    height: 495px;
    float: left;
    color: rgba(0, 0, 0, 1);
}


section is pretty much the same as sidebar, but has a larger width, with a 5 pixel left margin.

index.html
.main
{
    width: 800px;
    height: 495px;
    margin-top: 5px;
    float: left;
}

.sidebar
{
    width: 200px;
    height: 495px;
    float: left;
    color: rgba(0, 0, 0, 1);
}

.section
{
    width: 595px;
    height: 495px;
    margin-left: 5px;
    float: left;
}


You can see the outlined placeholders for the two additional divs we made.




Prettying it up

We don't usually do it this early, but we might as well get it out of the way. Let's use the image below for this.

bg.jpg

We then set the background image for the container CSS class. We will also give it a broad yellow outline.

index.html
.container
{
    width: 800px;
    height: 600px;
    padding: 5px;
    margin: 10px auto 0 auto;
    outline: 3px solid rgba(255, 255, 0, 1);
    background: url(bg.jpg) center center no-repeat;
    background-size: cover;

}


Looking good!




Back to business...

Add more HTML within the div that has been styled using the sidebar CSS class. There are h2 tags, and divs. They are styled using classes that we don't define because they're not there for styling. I think it's all pretty self-explanatory if you look at the naming convention.

index.html
<div class="main">
    <div class="sidebar">
        <h2>Time Left</h2>
        <div class="timeleft">

        </div>

        <h2>Inventory</h2>
        <div class="inventory">

        </div>

    </div>

    <div class="section">

    </div>
</div>


You can see what the HTML content looks like.




Now we fill up the other div. These divs have classes content and choices. These will have styling, and that is what we will do next.

index.html
<div class="main">
    <div class="sidebar">
        <h2>Time Left</h2>
        <div class="timeleft">

        </div>

        <h2>Inventory</h2>
        <div class="inventory">

        </div>
    </div>

    <div class="section">
        <div class="content">

        </div>

        <div class="choices">

        </div>

    </div>
</div>


The widths for choices and content are the same - 585 pixels. It's calculated after taking the original width of container and deducting the width of sidebar and the paddings. We set heights for both these classes, and a translucent black background. For content, the overflow property is set to auto because we anticipate that the HTML content might be too big for the div.

index.html
.sidebar
{
    width: 200px;
    height: 495px;
    float: left;
    color: rgba(0, 0, 0, 1);
}

.section
{
    width: 595px;
    height: 495px;
    margin-left: 5px;
    float: left;
}

.content
{
    width: 585px;
    height: 270px;
    overflow: auto;
    float: left;
    background-color: rgba(0, 0, 0, 0.7);
    color: rgba(255, 255, 255, 1);
    padding: 5px;
}

.choices
{
    width: 585px;
    height: 200px;
    margin-top: 5px;
    float: left;
    text-align: right;
    background-color: rgba(0, 0, 0, 0.7);
    color: rgba(255, 255, 255, 1);
    padding: 5px;
}


And now we see the sections!




More styling

Here's some more styling. We won't see the results right off, but let us get it out of the way.

This is for images in the content. We want a fixed width and height, and we want it centered.

index.html
.top h1
{
    font-size: 3em;
    font-weight: bold;
}

p img
{
    display: block;
    margin: 0 auto 0 auto;
}


.main
{
    width: 800px;
    height: 495px;
    margin-top: 5px;
    float: left;
}


And this is for buttons. I generally want a yellow background, but do what you want here.

index.html
p img
{
    display: block;
    margin: 0 auto 0 auto;
}

button
{
    background-color: rgba(255, 255, 0, 1);
    color: rgba(255, 255, 255, 1);
    width: 3em;
    padding: 0.2em;
    border-radius: 5px;
    border: 0px solid red;
    display: inline-block;
    margin: 0 0 0.5em 1em;
}

button:hover
{
    background-color: rgba(255, 255, 0, 0.5);
    color: rgba(0, 0, 0, 1);
}


.main
{
    width: 800px;
    height: 495px;
    margin-top: 5px;
    float: left;
}


At this point, it should be safe to remove the red outline.

index.html
div {outline: 0px solid #FF0000;}


Here we go! You have a visual framework for which the rest of the output will appear. As mentioned before, you will see no yellow buttons, for the simple reason that we have not yet placed any in the HTML.




Finally, let's have the message CSS class. It's really just for aesthetics, and you will see it later on. It has a translucent black background and red text, and some nice round corners.
.section
{
    width: 595px;
    height: 495px;
    margin-left: 5px;
    float: left;
}

.message
{
    width: 80%;
    padding: 1em;
    margin: 0 auto 0 auto;
    background-color: rgba(0, 0, 0, 0.5);
    color: rgba(255, 0, 0, 1);
    border-radius: 5px;
    text-align: center;
}


.content
{
    width: 585px;
    height: 270px;
    overflow: auto;
    float: left;
    background-color: rgba(0, 0, 0, 0.7);
    color: rgba(255, 255, 255, 1);
    padding: 5px;
}


Next

We will do some simple setup for program objects and methods.