megacolorboy

Abdush Shakoor's Weblog

Writings, experiments & ideas.

Getting started with Laravel Homestead

Wanted to install Laravel but scared of configuring files on your host operating system? This article is for you.

I started learning Laravel and I thought of writing a small tutorial on how to setup your own local development environment for Laravel projects with Laravel Homestead and Vagrant.

What is Laravel Homestead?

It's an official package from Laravel that provides a neat development environment that comes along with a Vagrant box that has pre-installed software. Read their documentation to know more about it.

Note: The examples of this tutorial are applicable to any developer that uses any UNIX-based operating systems such as macOS or any Linux distributions like Ubuntu. If you're using Windows, I recommend that you install Git for Windows on your local machine.

Prerequisites

The only requirement for this tutorial is that you should be familiar with using the command-line interface.

Install VirtualBox and Vagrant

VirtualBox is the software used to run a virtual machine with a sandbox operating system within your own computer.

Vagrant is a software that's used to manage a development environment. Through the command-line, you can perform any operation such as installing an OS, configuration, deployment and so on.

You can install VirtualBox and Vagrant via the command-line:

sudo apt install virtualbox vagrant

Once you're done installing them, you should add the laravel/homestead box to your Vagrant installation using the following command:

vagrant box add laravel/homestead

To check if it has been installed, use the following command:

vagrant box list | grep "homestead"

Using VirtualBox and Vagrant allows you to simulate your Laravel development environment without messing up any configurations on your hosting system. Even if you did, no worries, Vagrant boxes are disposable, so you can destroy and create a new box again in minutes.

Download Laravel Homestead

You can install Homestead by cloning it's repository onto your local machine by typing the following command:

git clone https://github.com/laravel/homestead.git ~/projects/Homestead

Generate configuration file

Once you're done cloning the repository, go inside the projects/Homestead directory and run the init.sh or init.bat (for Windows) to create the Homestead.yaml configuration file:

cd projects/Homestead
bash init.sh

Open the default Homestead.yaml file and make the following changes:

---
ip: "192.168.10.10"
memory: 2048
cpus: 2
provider: virtualbox

authorize: ~/.ssh/id_rsa.pub

keys:
    - ~/.ssh/id_rsa

folders:
    - map: ~/projects/code
      to: /home/vagrant/projects/code

sites:
    - map: homestead.test
      to: /home/vagrant/projects/code/public

databases:
    - homestead

Generate SSH key

The documentation for Laravel Homestead doesn't really talk about generating SSH keys used to access the Vagrant box. Use the following command to generate it using ssh-keygen on the command-line:

ssh-keygen -t rsa -C "root@homestead"

Map the project's shared folder

Make sure that you have created a folder named code in your projects directory. This folder will keep all of your Laravel project files in sync between your local machine and Homestead environment.

Installing Laravel

Time to install Laravel into your virtual machine. So, get switch on your virtual machine by doing the following in the command-line:

vagrant up

As you're switching it on for the first time, depending on your internet connection, it might take somewhere around 10-20 minutes to getting it running but it'll be fine afterwards.

Alright, login to your Vagrant machine by doing the following the command-line:

vagrant ssh

Once you're in, go to your code directory and type the following in the command-line:

composer global require laravel/installer

Create a new project

Now, it's time to create a new Laravel project by simply typing the following command:

laravel new projectname

It'll take some time to generate the necessary files and boilerplate code and after that, you're good to go!

Oh, you should be able to see the Laravel project files in your local machine as it's synced with the virtual machine and local machine. That means, any changes made on either machines will be synced and reflected on both ends.

Map your hosts file

One last step, make sure you map your project's test domain onto your local machine's hosts file.

Open a text-editor of your choice and do the following in the etc/hosts file:

127.0.0.1   localhost
::1         localhost

# Add this line (Note: It can be any URL)
127.0.0.1   homestead.test

Now, go to your browser and type homestead.test and you should be able to see your Laravel project's sample layout.

Conclusion

As mentioned, in the beginning of this article, I have started to learn Laravel and I'll be building some projects and writing out my experiences about it soon.

I hope you liked reading this article and please share it with others if you do find this tutorial useful.

Stay tuned for more!

Build your own Static Site Generator using Python

Are you curious enough to build your own Static Site Generator? This article is for you.

If you are curious about web development, you must have came across names like Jekyll, Hugo, Gatsby and more. These names that I mentioned are called Static Site generators. In fact, this website that you're on, is powered by a static site generator and I really like it so far.

In today's article, you'll learn how to build your own Static Site Generator using Python, create a blog and host it on Netlify for free. Please note, that this is a basic tutorial that would show the bare-metals of this generator.

These are the pre-requisities to this tutorial:

  • Basic understanding of Python, Git, HTML and Markdown
  • Basic knowledge of file I/O
  • Know-how on using the command-line

If you don't know them, it's okay, you can still do your own research on Google and learn on-the-go!

Before we jump in, let's see why is it the latest craze!

Why is it popular?

Take a blog, it's a dynamic web application that consists of a lot of sections like blog posts, archives and categories. Each time, a user visits a blog post, it sends a GET request to the server, fetches the data, then generates a webpage along with the fetched data on-the-fly. However, with a static site generator, you only serve pre-rendered files, so you don't have to generate or create anything on-the-fly, it's already there!

Apart from that, applications like a blog are usually database-dependant and you need to host it somewhere with a price. Not only that, but it does have security risks. With static site generators, you write your content on a markdown file and then convert it into static HTML files, which can then be used to host your website on services like GitHub Pages or Netlify for free and you don't have to worry about any security issues.

What are the functionalities?

These are the functionalities that we'd require in a blog:

  • Display all blog articles
  • Pagination module
  • A page for each article

So, let's go ahead and start with building the project.

Time to build it!

Let's plan before we dive in straight into coding it. This is the file structure we'll be following in this tutorial:

Create file structure

This is the file structure we're going to follow in this tutorial, so create it before you proceed with the tutorial.

\blog-ssg
    \content
    \output
    \templates
    make_entry.py
    ssg.py
    ssglib.py

Next, install the following dependencies using pip:

pip install markdown2
pip install jinja2

Generate markdown files

Go to your make_entry.py file and copy-paste this code. This will be used to generate markdown file for you to edit instead of having to create it manually all the time.

import os, sys
from glob import glob
from datetime import datetime

# This is the Markdown template
TEMPLATE = """
title: {title}
date: {date}
slug: {slug}
category: Write category here.
summary: Write summary here.

Write content here.
"""

def suffix(d):
    return 'th' if 11<=d<=13 else {'1':'st', '2':'nd', '3':'rd'}.get(d%10, 'th')

def custom_strftime(format, t):
    return t.strftime(format).replace('{S}', str(t.day) + suffix(t.day))

# Generate file number by looking at the number blog articles in the directory
def gen_file_no(file_path, existing_files):
    """
        If no files are found,
        then this is the first file.
    """
    if not existing_files:
        return 1

    """
        Look for the biggest file number
        and return the bigger one
    """
    prefixes = [filename.replace(file_path,"")[:2] for filename in existing_files]
    biggest_prefix = max(prefixes, key= lambda n: int(n))
    return int(biggest_prefix) + 1

# Generate slug for the blog article
def gen_slug(title):
    title = "".join(x for x in title if x.isalnum() or x == ' ')
    title = title.lower().strip("").replace(' ','-')
    return title

# Create entry
def make_entry(title, file_type):
    if file_type == "articles":
        file_path = "content/" + file_type + "/"

        # Create folders if they didn't exist
        if not os.path.exists(file_path):
            os.makedirs(os.path.dirname(file_path))

        today = datetime.today()

        slug = gen_slug(title)
        file_no = gen_file_no(file_path, existing_files=glob(file_path + "*.md"))
        blog_file_path = file_path + "{:0>2}_{}_{:0>2}_{:0>2}_{}.md".format(file_no, today.year, today.month, today.day, slug)

        post_date = datetime.strptime(str(today.date()), '%Y-%m-%d')
        post_date = custom_strftime('%B {S}, %Y', post_date)

        t = TEMPLATE.strip().format(title=title,
            year=today.year,
            month=today.month,
            date=post_date,
            slug=slug)      

        with open(blog_file_path, 'w') as w:
            w.write(t)

        print blog_file_path

if __name__ == '__main__':
    if len(sys.argv) > 1:
        make_entry(sys.argv[1], sys.argv[2])
    else:
    print "Enter blog title and article type"

To create a blog entry, just type this in your terminal:

python make_entry.py "Article Name" articles

Create a library

This library must have the following functionalities:

So, create a new file named ssglib.py and let's do each of the functionality step-by-step to gain a deeper understanding of each method/function in the library.

Before that, add the following lines to the file:

import os, random, sys
from datetime import datetime
from jinja2 import Environment, PackageLoader
from markdown2 import markdown
from argparse import Namespace

# Jinja2 Environment
env = Environment(loader=PackageLoader('ssg','templates'))

Fetch content from Markdown files

This method will fetch content from all of the Markdown files that resides in the content/articles folder.

# Get Markdown files from content directory
def get_markdown_files(section):
    POSTS = {}

    file_path = 'content/'

    for md_file in os.listdir(file_path):
        md_file_path = os.path.join(file_path, md_file)

        # if it's not a directory, read it
        if not os.path.isdir(md_file_path):
            with open(md_file_path, 'r') as file:
                POSTS[md_file] = markdown(file.read(), extras=['metadata'])

    return POSTS

Create templates

You'll need to create two templates, one for home page and the other is for your articles. All templates will reside in the templates folder.

This is the template for the home page of your blog and you can name this as index.html:

<!DOCTYPE html>
<html>
<head>
    <title>My Blog</title>
    <base href="/"/>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <main>
        <div class="posts-list">
            {% for post in posts %}
            <a href="posts/{{ post.slug }}/">
                <p>{{ post.date }} &mdash; {{ post.title }}</p>
            </a>
            {% endfor %}
        </div>

        <div class="paginator">
            {% if curr == 2 %}
                <a class="prev" href="/" rel="prev">Newer</a>
            {% elif prev != 0 %}
                <a class="prev" href="/pages/{{prev}}" rel="prev">Newer</a>
            {% else %}
                <a class="prev inactive">Newer</a>
            {% endif %}

            <p>{{curr}} <span>of</span> {{total}}</p>

            {% if next != 0 %}
                <a class="next" href="/pages/{{next}}" rel="next">Older</a>
            {% else %}
                <a class="next inactive">Older</a>
            {% endif %}
        </div>

    </main>
</body>
</html>

This is the template for the article page and you can name this as article.html:

<!DOCTYPE html>
<html>
<head>
    <title>{{ post.title }}</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="author" content="Your name">
    <meta name="description" content="{{ post.summary }}.">
</head>
<body>
    <main class="article-main">
        <div class="article-info">
            <div>
                <h5>{{ post.category }}</h5>
                <h1>{{ post.title }}</h1>
                <p>{{ post.summary }}</p>
                <h6>Your name &mdash; {{ post.date }}</h6>
            </div>    
        </div>
        <article class="article-container">
            <div class="content">
            {{ post.content }}
            </div>
        </article>
    </main>
</body>
</html>

Though, this is just a bare-bone template, you can be creative and make your own changes to it by making it visually-appealing!

Generate index page

This method will create your index page. The index page will contain the links for each article.

def index(POSTS, isPaginate=True):
    posts_metadata = get_posts_metadata(POSTS)
    if not isPaginate:
        template = env.get_template('index-alt.html')
        html_content = template.render(posts=posts_metadata)
        index_path = 'output/index.html'
        write_to_file(index_path, html_content.encode('utf-8'))
    else:
        args = {
            "template": env.get_template('index.html'),
            "posts_metadata": posts_metadata,

            # Keeps tracks the current post
            "curr_posts_index": 0,

            # Total number of posts
            "total_posts": len(POSTS),

            # Number of posts per page
            "posts_per_page": 8,

            # Directory to hold the pages
            "main_pages_path": "output/pages/"
        }
        pagination(args)

Generate articles from Markdown to HTML

This method will convert your Markdown content into HTML pages. One thing to note, each blog post will have it's own folder name with it's own slug and index.html file, so that it'll be easier to access the article.

# Generate all posts
def articles(POSTS, post_template):
    for post in POSTS:
        post_metadata = POSTS[post].metadata
        post_data = {
            'content': POSTS[post],
            'slug': post_metadata['slug'],
            'title': post_metadata['title'],
            'summary': post_metadata['summary'],
            'category': post_metadata['category'],
            'date': post_metadata['date']
        }
        post_html_content = post_template.render(post=post_data)
        post_file_path = 'output/posts/{slug}/index.html'.format(slug=post_metadata['slug'])
        create_directory(post_file_path)
        write_to_file(post_file_path, post_html_content.encode('utf-8'))

Generate pagination module

Depending on the amount of blog articles you want to display, it will create total number of posts / article per page pages on the home page. They will look identical to the index page and these pages will be created in output/pages folder.

# Pagination module
def pagination(args):

    x = Namespace(**args)

    # Number of pages (for pagination)
    num_pages = (x.total_posts/x.posts_per_page)

    # Create a page directory, if it doesn't exist
    create_directory(x.main_pages_path) 

    for pagenum in range(0, num_pages+1):
        # This will contain metadata of every x number of posts per page
        page_metadata = []
        page_path = ""
        curr_page = pagenum+1

        if curr_page == 1:
            page_path = "output/index.html" 
        else:
            page_path = "output/pages/{page}/index.html".format(page=curr_page)

        create_directory(page_path)

        # Internal counter to keep track of posts per page
        posts_counter = 0

        for j in range(x.curr_posts_index, len(x.posts_metadata)):
            page_metadata.append(x.posts_metadata[j])
            posts_counter = posts_counter+1

            # If it reached it's limit, break
            if posts_counter == x.posts_per_page:
                x.curr_posts_index = j+1
                break

        # Create links for previous and next pages
        prev_page = 0 if curr_page == 1 else curr_page-1
        next_page = 0 if curr_page == num_pages+1 else curr_page+1

        # Render the page
        html_content = x.template.render(
            posts=page_metadata, 
            curr=curr_page,
            prev=prev_page, 
            next=next_page,
            total=num_pages+1
        )

        write_to_file(page_path, html_content.encode('utf-8'))

Generate blog

This is the main function that will generate the entire blog including all of the article pages.

# Generate blog -- Main function
def main(section):
    sections = ['articles']
    if section in sections:
        POSTS = get_markdown_files(section)
        if section == "articles":
            posts_template = env.get_template('article.html')
            index(POSTS, True)
            articles(POSTS, posts_template)
    else:
        print "This section doesn't exist."

Take a look at the source code for both ssglib.py and ssg.py and feel free to tinker around with it.

Source code for ssglib.py:

#!usr/bin/python
import os, random, sys
from datetime import datetime
from jinja2 import Environment, PackageLoader
from markdown2 import markdown
from argparse import Namespace

# Jinja2 Environment
env = Environment(loader=PackageLoader('ssg','templates'))

# Write to file
def write_to_file(path, content):
    with open(path, 'w') as file:
        file.write(content)
        file.close()

# Create directory
def create_directory(path):
    if not os.path.exists(path):
        os.makedirs(os.path.dirname(path))

# Get Markdown files from content directory
def get_markdown_files(section):
    POSTS = {}

    file_path = 'content/'

    for md_file in os.listdir(file_path):
        md_file_path = os.path.join(file_path, md_file)

        # if it's not a directory, read it
        if not os.path.isdir(md_file_path):
            with open(md_file_path, 'r') as file:
                POSTS[md_file] = markdown(file.read(), extras=['metadata'])

    return POSTS

"""
Collect metadata of all the posts
for home page and sort them 
in reversed order

@param: 
    POSTS => Dictionary
"""
def get_posts_metadata(POSTS):
    posts_metadata = []
    for k,v in sorted(POSTS.items(), reverse=True):
        posts_metadata.append(v.metadata)
    return posts_metadata

"""
    @params:
    POSTS => Dictionary
    template => jinja template
    isPaginate => boolean to enable pagination
"""
def index(POSTS, isPaginate=True):

    posts_metadata = get_posts_metadata(POSTS)

    if not isPaginate:
        template = env.get_template('index-alt.html')
        html_content = template.render(posts=posts_metadata)
        index_path = 'output/index.html'
        write_to_file(index_path, html_content.encode('utf-8'))

    else:
        args = {
            "template": env.get_template('index.html'),
            "posts_metadata": posts_metadata,

            # Keeps tracks the current post
            "curr_posts_index": 0,

            # Total number of posts
            "total_posts": len(POSTS),

            # Number of posts per page
            "posts_per_page": 8,

            # Directory to hold the pages
            "main_pages_path": "output/pages/"
        }

        pagination(args)

# Generate all posts
def articles(POSTS, post_template):
    for post in POSTS:
        post_metadata = POSTS[post].metadata
        post_data = {
            'content': POSTS[post],
            'slug': post_metadata['slug'],
            'title': post_metadata['title'],
            'summary': post_metadata['summary'],
            'category': post_metadata['category'],
            'date': post_metadata['date']
        }
        post_html_content = post_template.render(post=post_data)
        post_file_path = 'output/posts/{slug}/index.html'.format(slug=post_metadata['slug'])
        create_directory(post_file_path)
        write_to_file(post_file_path, post_html_content.encode('utf-8'))

# Generate pages
def pagination(args):

    x = Namespace(**args)

    # Number of pages (for pagination)
    num_pages = (x.total_posts/x.posts_per_page)

    # Create a page directory, if it doesn't exist
    create_directory(x.main_pages_path) 

    for pagenum in range(0, num_pages+1):
        # This will contain metadata of every x number of posts per page
        page_metadata = []
        page_path = ""
        curr_page = pagenum+1

        if curr_page == 1:
            page_path = "output/index.html" 
        else:
            page_path = "output/pages/{page}/index.html".format(page=curr_page)

        create_directory(page_path)

        # Internal counter to keep track of posts per page
        posts_counter = 0

        for j in range(x.curr_posts_index, len(x.posts_metadata)):
            page_metadata.append(x.posts_metadata[j])
            posts_counter = posts_counter+1

            # If it reached it's limit, break
            if posts_counter == x.posts_per_page:
                x.curr_posts_index = j+1
                break

        # Create links for previous and next pages
        prev_page = 0 if curr_page == 1 else curr_page-1
        next_page = 0 if curr_page == num_pages+1 else curr_page+1

        # Render the page
        html_content = x.template.render(
            posts=page_metadata, 
            curr=curr_page,
            prev=prev_page, 
            next=next_page,
            total=num_pages+1
        )
        write_to_file(page_path, html_content.encode('utf-8'))

# Generate blog -- Main function
def main(section):
    sections = ['articles']
    if section in sections:
        POSTS = get_markdown_files(section)
        if section == "articles":
            posts_template = env.get_template('article.html')
            index(POSTS, True)
            articles(POSTS, posts_template)
    else:
        print "This section doesn't exist."

Source code for ssglib.py

import ssglib
from ssglib import (os, random, sys, datetime, 
Environment, PackageLoader, markdown)

if __name__ == '__main__':
    if len(sys.argv) > 1:
        ssglib.main(sys.argv[1])
    else:
        print "Enter section name you want to generate."

To generate your blog, type the following in your command-line:

python ssg.py articles

Make sure you have a local HTTP server running on your computer, if you don't install this using NPM on your command-line:

npm i live-server

Alright, your blog is generated and now, it's time to launch it to the internet!

Deploy project on Netlify

As I had mentioned in the previous article, I'm currently hosting my blog on Netlify. If you want to know more about it, click here.

Let's go through the steps to deploying your blog:

1. Create a GitHub repository

If you have a GitHub account, continue reading. If not, go here.

Create a repository with any name you like and push all your files into the repository.

2. Connect to Netlify

Create an account on Netlify.com and then connect to your GitHub account. After that, you have to select the repository that has your static files (Not the python code).

3. Sit and Relax

At this stage, your blog will be deployed automatically and you'll be given a unique URL to view your website.

If you have a custom domain, I suggest you follow the instructions and make the necessary changes in the CNAME and A records.

Don't worry about the SSL certificate, it does take some time to activate, it took me like 7-8 hours to get it working.

Conclusion

If you've completed this tutorial, give a yourself a pat in the back because now you know how static site generators work and you've built and hosted your own blog on the internet.

Hope you liked reading this article and please share it to others too!

Stay tuned for more!

Migrating to Netlify

My thoughts on why I made the switch and how it improves my workflow.

When I converted my website to a static site using a custom-built static site generator using Python, I came across topics like "Hosting your website on GitHub Pages". At the time, I thought that I didn't really need it because my website was hosted on Hostinger and it pretty much had everything that I needed until it started to get on my nerves.

Recently, my blog had been facing a lot of issues such as my website would face random downtime, hosting prices wasn't consistent and the FTP service for it was really annoying.

So, I decided that I need to find a better hosting service for my blog.

Exploring GitHub Pages

Setting up your website was pretty easy using GitHub Pages, all you have to do was create a repository named websitename.github.io and you're done.

Plus, unlike other hosting services, GitHub Pages hosts your website for free. That sounded like really amazing and it has really good security, caching and speed too.

However, it was my first choice and like every other free hosting services, it does come with a few drawbacks that didn't fit my requirements. GitHub Pages offers a bandwidth limit of 1GB and it didn't have good custom domain support. Also, I read that it had weird caching issues once you commit your changes to your repository.

Here comes, Netlify!

When I discovered Netlify, I was happy because it had free hosting, good custom domain support and a 100GB soft bandwidth, which is really fitting my requirements.

According to Netlify:

Make your site or web-app many times faster by bringing it closer to your users.

The cool thing is that your website is not only pushed to a single server but to a global network of CDN nodes that can handle operations like automatic caching, smart rewrite and redirect rules and yes, blazing, fast loading speeds.

Hosting your website on Netlify is simple as few clicks, all you have to do is upload your website on a Git repository then connect it to Netlify and it will deploy your website in less than 5 minutes.

Updating my blog is easier now as now I just have to push my new commits to my GitHub repo and Netlify would automatically deploy it. Goodbye, FTP!

Configuring HTTPS and custom domain settings

Since I have a custom domain, I configured my domain to point to Netlify's load balancer IP address by changing the A and CNAME records in the DNS Zone editor. The HTTPS/SSL part was a bit annoying and it took some time, like 5-6 hours, but apart from that, it was a great experience in migrating to Netlify.

Conclusion

In short, my blog's static files are hosted on GitHub and automatically deployed on Netlify for free. I do have some future plans, like trying to roll out a commenting engine and light/dark themes for the blog.

Hope you liked reading this article!

Stay tuned!

Minesweeper Clone

Wrote an old classic puzzle game using Javascript.

Before you read this article, play with the above game. The rule is simple, reveal one cell at a time by clicking on them, If the cell you've clicked on is clear, you'll see the number of mines that's adjacent to it. Else, if it's a mine, then all mines will explode and you lose the game. Reload the game by clicking the Reset button.

The source code of the game can be found over here.

Background

It's a classical puzzle game that gained immense popularity when it came along with Windows 3.1 OS. To be honest, I never really had any experience playing this game until a few days ago, I had a sudden curiosity as to how it works. I studied it's game mechanics on Wikipedia and it turned to be simple enough to write a clone in Javascript.

Game mechanics

What makes it interesting to play is it's simplicity in which all that matters is that you shouldn't click the cell that goes "KABOOM!".

Here are some of the game mechanics:

Generate cells

Generating the grid is pretty much straightforward. Each cell will have an attribute named data-mine with a boolean value of true or false.

//Generate minesweeper grid
const generateGrid = () => {
    allowClick = true;
    grid.innerHTML = "";
    for(let i=0; i<size; i++) {
        let row = grid.insertRow(i);
        for(let j=0; j<size; j++) {
            let cell = row.insertCell(j);
            cell.onclick = function(){clickCell(this);}
            let mine = document.createAttribute("data-mine");
            mine.value = "false";
            cell.setAttributeNode(mine);
        }
    }
    setMines();
}

Placing the mines

After the grid is generated, a mine will be added to each random cell.

//Set mines
const setMines = () => {
    for(let i=0; i<size*2; i++) {
        let r = Math.floor(Math.random() * size);
        let c = Math.floor(Math.random() * size);
        let cell = grid.rows[r].cells[c];
        cell.setAttribute("data-mine", "true");
        if(testMode){cell.innerHTML = "&#x1f4a3;";}
    }
}

Reveal the mines

If you've clicked on a mine, every cell that has a mine will be exposed and then it's game over.

//Reveal mines
const revealMines = () => {
    for(let i=0; i<size; i++) {
        for(let j=0; j<size; j++) {
            let cell = grid.rows[i].cells[j];
            if(cell.getAttribute("data-mine") === "true") {
                cell.className = "mine";
                cell.innerHTML = "&#x1f4a3;";
            }
        }
    }
}

Scan for mines

When you click on a tile, there are 2 possibilities i.e. either the cell is a mine or not.

If it's a mine, we know what happens, it's self explanatory. If it's not a mine, what happens then? Well, it'll start scanning for mines that are adjacent to it in all eight directions:

Direction Coordinates
Top(row - 1, col)
Bottom(row + 1, col)
Left(row, col - 1)
Right(row, col + 1)
Top Left(row - 1, col - 1)
Top Right(row - 1, col + 1)
Bottom Left(row + 1, col - 1)
Bottom Right(row + 1, col + 1)

If there aren't any mines adjacent to it, it'll reveal all adjacent cells via recursion.

    //Click a cell
    const clickCell = (cell) => {
        if(allowClick != false) {
            //If it's a mine, game over
            if(cell.getAttribute("data-mine") === "true") {
                alert("game over");
                revealMines();
                allowClick = false;
            }
            //If it's not a mine, reveal the mines
            else {
                //Mark it as "clicked"
                cell.className = "clicked";
                scanForMines(cell);
                checkGameStatus();
            }       
        }
    }

    //Scan for mines that are adjacent to the cell
    const scanForMines = (cell) => {
        let rowPos = cell.parentNode.rowIndex;
        let colPos = cell.cellIndex;
        let mineCount = 0;

        for(let i=Math.max(rowPos-1, 0); i<Math.min(rowPos+1, size-1); i++) {
            for(let j=Math.max(colPos-1, 0); j<Math.min(colPos+1, size-1); j++) {
                let adjacentCell = grid.rows[i].cells[j];
                if(adjacentCell.getAttribute("data-mine") == "true") {
                    mineCount++;
                }
            }
        }

        cell.innerHTML = mineCount > 0 ? mineCount : " ";

        //If zero mines, then reveal all adjacent cells
        if(mineCount == 0) {
            for(let i=Math.max(rowPos-1, 0); i<Math.min(rowPos+1, size-1); i++) {
                for(let j=Math.max(colPos-1, 0); j<Math.min(colPos+1, size-1); j++) {
                    let adjacentCell = grid.rows[i].cells[j];
                    if(adjacentCell.innerHTML == "") {
                        clickCell(adjacentCell);
                    }
                }
            }
        }
    }

Level completion

If the player completed the game without clicking on any mine, the level is complete.

    //Check game status
    const checkGameStatus = () => {
        let levelComplete = true;
        for(let i=0; i<size; i++) {
            for (let j=0; j<size; j++) {
                var cell = grid.rows[i].cells[j];
                if((cell.getAttribute("data-mine") == "false") && (cell.innerHTML == "")) {
                    levelComplete = false;
                }
            }
        }

        if(levelComplete) {
            alert("Congratulations, you won!");
            revealMines();
        }
    }

Conclusion

Well, that's about it. I had fun writing this game. Do you want me to build more games like this? Send me an email about it and I'll see what I can do from my end.

Have fun playing the game and oh, don't blow yourself up! 😜

Hope you liked reading this article!

Stay tuned for more!

Optimize your website performance

Sharing my experiences on website optimization.

This article is directed towards audiences who are keen on making their websites and web applications load faster and perform better.

I won't be focusing on the SEO (Search Engine Optimization) part of it but rather, I'll be covering on how to overcome performance bottleneck issues. If you want to know more about SEO, you can go here.

Recently, I boosted my company's website from a lagging 10-30 seconds (I know, that's really embarassing!) to a smooth 1.7 to 2.5 seconds load speed, so that's why I thought of sharing my experiences and tips on this article.

Before we get there, let's first understand what could probably make your website perform bad or load slowly. Afterwards, there'll be some strategies on how you could fix those issues and make it optimal.

Why is my website so slow?

You're not a developer if you've never heard this in your day-to-day life. In today's era, the standard average page load time is under 3 seconds, anything more than that, you might lose a visitor and that can affect your website's SEO in the long run.

Here are some of the reasons, in my opinion:

Too many HTTP requests

According to GTMetrix, a web page should have an average of 92 requests but there are websites that have over 100+ requests and that's crazy, especially, if you try to access the website on a slower network.

There can be many factors that contributes to an increase in HTTP requests:

  • Too many images on a page
  • CSS and JS files are not compressed and minified
  • Calling external libaries like jQuery or Bootstrap from a Content-Delivery Network
  • Loading all JS files at one go

Since, each DOM element is generated in a top-down manner, it would display the completed page when all resources are loaded, hence is why you shouldn't have too many server requests.

Inefficient database queries

Let's say, you have a page that displays a list of products from your SQL database and you execute this query:

SELECT * FROM products

Sorry, it's inefficient. Why? In a real-life scenario, a table may contain a lot of columns and multiply that with x number of database calls the server has to make for EVERY request that comes from a visitor. You get it, right?

This could be worse if you have an unorganized database structure. Plan well, my friend.

Images aren't optimized

Images that are uncompressed, have a higher resolution or large file size can be detrimental to the site's performance which creates an unpleasant user experience.

Improper page caching

If you've built a dynamic page that displays a blog article, it probably performs a lot of requests/callbacks to generate a page with necessary information. Multiply that with x number of user sessions and that'll place a huge workload on the server.

Your codebase stinks

This is a very deep problem especially if the code is never refactored in a very long time.

Common causes of code smells are:

  • Duplicate code
  • Large methods or classes
  • Too many parameters
  • Long line of code
  • Unnecessary uses of loops and conditions
  • Unused code

There's much more to this but if you're facing this, it's about time that you start fixing your codebase and keep it mint condition.

Shared Server Hosting

Ah, this is the most common culprit. Many people host their websites on a shared server and guess what? Shared hosting means shared system resources because apart from your website, there could be 5 or more websites hosted on the same server.

How to overcome these issues?

Before we move on, let me just clarify that the techniques I've used below worked out for me but it may or may not work for you.

Here are the following methods that worked out for me:

Compress & Resize Images

I'm sure that there are multiple ways to compress and resize an image like using Adobe Photoshop but what if there are hundreds and thousands of images? It'd be time consuming to do all of that.

I created an ad hoc solution using a PHP-based image extension named ImageMagick. This library is used to create and modify images using the ImageMagick Library.

Since it can be used directly from the terminal, I wrote a small script using PowerShell to compress and resize the images in all folders in one go!

Snippet to resize images:

magick mogrify -resize 256x256 -format *.jpg *.jpeg *.png

Snippet to compress images:

magick mogrify -quality 70% *.jpg *.jpeg *.png

The logic of the code is pretty straightforward. All it has to do is get a list of files with ends with image extensions (.jpg, .jpeg, .png) and modify the resolution to 640x480 and reduce the quality to a 70%.

Previously, all images of the website combined was around 800MB and after executing the script, it went down to a mere 70MB. The quality of the images weren't ruined and the aspect ratio didn't change. That's quite astounding.

Minify Javascript

Ever inspected someone's CSS or JS code on Chrome Debugger and saw something like this:

for(var a=[i=0];i<20;a[i]=i++);

This code above is unreadable but it still retains it's previous functionality:

var array = [];
for (var i = 0; i < 20; i++) {
    array[i] = i;
}

This process is known as minification. It's a way of removing all spaces, unnecessary characters and comments that would help reduce the size of the source code and make it more efficient to be transferred over the network.

Using a module bundler like Webpack, you can minify and merge all of your Javascript files into one sizeable chunk. Webpack's documentation is one of the worst that I have seen and I had to take help from StackOverflow to figure it out but however, it gets the job done.

Want to know to configure it? Check out this thread from StackOverflow for more in detail.

CSS Preprocessors to the rescue!

Trust me, these are life-savers. I used to hate writing a lot of CSS but now, I like writing CSS because of this reason. I chose SASS as my CSS preprocessor and it's basically CSS-on-steroids!

It's got some cool features like reusable CSS code, writing functions a.k.a @mixin and calling them using @include, nested syntaxes and import multiple CSS modules.

Writing in SASS is no different than writing in CSS but it allows you to modularize and organize your code in a fashionable manner. This allows you to even split CSS code to different modules, especially, if your HTML component uses a lot of CSS elements.

Oh, you can also create a minified version of your CSS code and push it into production.

Defer Javascript

The defer attribute used in the <script> tag will allow the JS file to be executed after the page has finished parsing all the DOM elements.

When the attribute is used, it'll look like this:

<script type="text/javascript" src="script.js" defer></script>

This could save a lot of time especially if it contains code that is not critical.

If you want to read more about it, click here.

Efficient database queries

As mentioned above, inefficient database queries could be detrimental to your website's performance. You can get rid of it with the following methods:

  • Maintain a good table structure
  • Keep your database architecture organized. Read more about Database Normalization
  • Keep your SQL query as simple as possible

Usage of clauses like WHERE, JOIN and GROUP BY are important but when it comes to performance, use it wisely.

PHP Page Caching

My company's website is mostly dynamic and I realized that all it does is fetch data from the servers to generate static information.

So, I thought of writing a caching module, in which, when any given page is accessed in the website, it would create a fully-generated static page with an expiry time of 5 minutes. So, when another user enters the same page, it will serve the static page, so no more delays for anyone.

This drastically reduced the load on the website's server because as mentioned above, it takes a lot of resources to create a page, dynamically.

You can find the source code of the caching module in my GitHub repository.

Refactor your codebase

Refactoring has a lot of benefits and in fact, it could reduce the size of your website, fix any unfixed bugs and more. All of which can contribute to an improved performance of your website.

So, please do yourself a favor and start cleaning up your codebase. If you don't know where to start, go here.

LazyLoad DOM Elements

Usually, whenever a page is opened by a user, it's contents are downloaded in a single go but this could be annoying if it disrupts the first view of the page.

So, what LazyLoad does is that instead of loading all of the content in a bulk, it only displays part of the content when a part of the page is accessed. The pages are created with a placeholder and will replace it with actual content only when the user needs it.

Examples of this can be seen in the form of:

  • Image galleries
  • Social media posts
  • Infinite scrolling
  • Pagination

This technique helps in performance but might create a negative impact on SEO in terms of webpage rankings.

If you want to give it a shot, try using this jQuery library.

Dedicated Server

Most hosting websites offer this service, you just have to buy a server and it would only host your website.

It's highly configurable and you won't have any issues regarding performance, however, it doesn't come without an expensive price tag on it but if you're willing to pay, it can be a great long-term investment.

Conclusion

If you've reached here, thank you for reading!

Let me know if any of these strategies have helped boost your website's performance and share it with other people too! Got more suggestions? Please send me an email and we can talk more about it.

Hope you liked reading this article!

Stay tuned for more!

Build your own toy spellchecker

Wrote a simple toy spellchecker using C++ by taking inspiration from Peter Norvig's article.

Spellcheckers and autocorrect, aren't they magical? They do feel like magic to me. I mean, how is it able to predict what word do you want to type?

According to Norvig, some of the most talented engineers at Google don't have a complete understanding of how a spellchecker works.

Peter Norvig's solution in Python is so elegant and simple, I don't think anyone can write better than that. However, I wanted to know how it works, so I thought of building it to understand it's functionality. Also, an excuse to exercise my C++ skills.

So, are you as curious as I am? If you are, I think you're in the right spot.

How it works?

Okay, so think about it? What does a spellchecker do? You type in a misspelled word and it returns a word with the highest probability, right?

Well, there's a little bit more to it.

Create a word dictionary

First, we must create a dictionary, in order to do that, you need to extract words from a large piece of text and store it in a Hashmap in which each word will have a word count. In this example, I've used a Sherlock Holmes novel (which is around 6MB). The words are extracted from a novel instead of an actual dictionary because it can be used to create a simple Language Model.

Source code to create a dictionary:

void SpellChecker::extractWords(string &filename)
{
    ifstream infile;
    infile.open(filename);
    string x;
    while(infile >> x)
    {
        x = filterAlpha(x);
        dictionary[x]++;
    }
}

string SpellChecker::filterAlpha(string &word)
{
    for(int i=0; i<word.size(); i++)
    {
        char ch = word[i];

        if(ch < 0)
        {
            word[i] = '-';
        }

        if(isalpha(ch))
        {
            word[i] = tolower(ch);
        }
    }
    return word;
}

Create a list of candidates

Second, we must able to predict/hypothesize the ways of editing text when the user types. It could be one of the following types of editing:

Based on the types of edits a user could make, we can generate a list of possible candidates by creating permutations using these edit methods mentioned above.

Adding a letter

In this method, you generate a list of candidates by inserting a letter in every iteration.

void SpellChecker::inserts(string &word, Vector &result)
{
    for(int i=0; i<word.size()+1; i++)
    {
        for(int j=0; j<alphabets.size(); j++)
        {
            char ch = alphabets[j];
            result.push_back(word.substr(0,i) + ch + word.substr(i));
        }
    }
}

Replacing a letter

In this method, you generate a list of candidates by replacing each character with a letter from a list of alphabets in every iteration.

void SpellChecker::replaces(string &word, Vector &result)
{
    for(int i=0; i<word.size(); i++)
    {
        for(int j=0; j<alphabets.size(); j++)
        {
            char ch = alphabets[j];
            result.push_back(word.substr(0,i) + ch + word.substr(i+1));
        }
    }
}

Switching two adjacent letters

In this method, you generate a list of candidates by switcing two adjacent letters in every iteration. For example: the word "ornage" would look like this: "orange", when the letters "n" and "a" are swapped.

void SpellChecker::transposes(string &word, Vector &result)
{
    for(int i=0; i<word.size()-1; i++)
    {
        result.push_back(word.substr(0,i) + word[i+1] + word[i] + word.substr(i+2));
    }
}

Removing a letter

In this method, you generate a list of candidates by removing a letter in every iteration.

void SpellChecker::deletes(string &word, Vector &result)
{
    for(int i=0; i<word.size(); i++)
    {
        result.push_back(word.substr(0,i) + word.substr(i+1));
    }
}

All of these methods are called in one wrapper method:

void SpellChecker::edits(string &word, Vector &result)
{
    //Deletion
    deletes(word, result);

    //Transposition
    transposes(word, result);

    //Replacement
    replaces(word, result);

    //Insertion
    inserts(word, result);
}

Extract the known words

Third, at this stage, the above step would've generated a huge list of words but 90% of them would be gibberish, so we need to "clean" the list and extract the known words using the dictionary we've created.

void SpellChecker::known_words(Vector& results, Dictionary &candidates)
{
    Dictionary::iterator end = dictionary.end();

    for(int i=0; i<results.size(); i++)
    {
        Dictionary::iterator val = dictionary.find(results[i]);

        if(val != end)
        {
            candidates[val->first] = val->second;
        }
    }
}

The edits() method apply to words that have a edit distance of 1, what if it was 2 or more? Like if the user typed "the", it could've been "then" or "they". So, all you have to do is create a method that generates a new set of permutations based on the already generated list of edited words and extract the known words.

void SpellChecker::edits2(Vector &result, Dictionary &candidates)
{
    for(int i=0; i<result.size(); i++)
    {
        Vector edit2;

        edits(result[i], edit2);
        known_words(edit2, candidates);
    }   
}

Display the correct word

In order to determine the correct word, the following possibilities are considered:

  1. Check if this word is in the dictionary, if it does, display it.
  2. Generate known words that have an edit distance of 1 and check in the dictionary, if it does, display it.
  3. Generate known words that have an edit distance of 2 and check in the dictionary, if it does, display it.
  4. If all else fails, this word is unique or not a known word.

string SpellChecker::correct(string &word)
{
    Vector result;
    Dictionary candidates;

    string file = "big.txt";

    //1. if it's in the dictionary, display it
    if(dictionary.find(word) != dictionary.end())
    {
        return word;
    }

    extractWords(file);

    edits(word, result);
    known_words(result, candidates);

    //2. if it's a known word but one edit away
    if(candidates.size() > 0)
    {
        return max_element(candidates.begin(), candidates.end())->first;
    }

    //3. if it's a known word but two edits away
    edits2(result, candidates);

    if(candidates.size() > 0)
    {
        return max_element(candidates.begin(), candidates.end())->first;
    }

    //4. Display nothing if it doesn't exist
    return "This word doesn't exist!";
}

However, for conditions 2 and 3, the word displayed would most likely have the highest word frequency in the dictionary.

Conclusion

Phew! I hope that wasn't a long read. Although, I've written this on C++, I plan to rewrite this on Javascript for some of my future projects.

To be honest, I don't think it's completely accurate, although, I got most of the words correct when tested.

The source code can be found on my GitHub repository.

Hope you liked reading this article!

Stay tuned for more!

The 15 Puzzle Game

A classical numbered puzzle that requires the player to place all the tiles in an ordered sequence.

Before you read this article, play with the above puzzle. You can move the block around by left-clicking on a numbered tile that's adjacent to the empty tile.

The source code for this puzzle can be found over here.

Background

This is a game that has been on my list of projects for a long time and I've finally decided to work on it last night. Although, this post has nothing to do with Artificial Intelligence, I was inspired to write this game when I studied about Heuristics in the book named Artificial Intelligence: A Modern Approach and on how it was applied to this game.

What are the game mechanics?

This game is played on a four-by-four grid with numbered tiles that are shuffled randomly. As you can see, there are 15 numbered cells and 1 empty cell in the grid, this is to allow movement of the tiles within the grid.

However, the movement is limited to the numbered tiles that are adjacent to the empty tile.

The player wins the game after ordering all the numbered tiles in the grid in an order of ascending sequence.

Source code

Here's the source code of the entire game:

var board = [], rows = 4, cols = 4;
var possibleMoves, zx, zy, oldzx = -1, oldzy = -1;

//Generate 2D Board
function generateBoard()
{
    for(var i=0; i<rows; i++)
    {
        board[i] = [];
    }

    for(var j=0; j<cols; j++)
    {
        for(var i=0; i<rows; i++)
        {
            board[j][i] = (i + j * 4) + 1;
        }
    }

    //position of the empty cell in the grid i.e. 3,3
    zx = zy = 3;
    board[zx][zy] = 16;
}

//Generate the cells
function generateCells()
{
    var grid = document.createElement("div");
    grid.className += "board";

    document.body.appendChild(grid);

    for(var j=0; j<4; j++)
    {
        for(var i=0; i<4; i++)
        {
            var cell = document.createElement("div");
            cell.className += "cell";
            cell.id = "cell_" + (i + j * 4);
            cell.row = i;
            cell.col = j;
            cell.addEventListener("click", cellEventHandle, false);
            cell.appendChild(document.createTextNode(""));
            grid.appendChild(cell);
        }
    }
}

/*
    Determine the possible number of moves
    based on the empty cell's coordinates.
*/
function genPossibleMoves()
{
    possibleMoves = [];
    var ii, jj;

    /*
        Just for reference:
        The empty cell can be moved in the following x,y coords:
        -1,0, 0,-1, 1,0, 0,1
    */
    var xCoords = [-1, 0, 1, 0];
    var yCoords = [0, -1, 0, 1];

    for(var i=0; i<4; i++)
    {
        ii = zx + xCoords[i];
        jj = zy + yCoords[i];

        //If it's out of bounds, skip it
        if(ii < 0 || jj < 0 || ii > 3 || jj > 3)
        {
            continue;
        }

        possibleMoves.push({x: ii, y: jj});
    }
}

function updateCells()
{
    for(var j=0; j<cols; j++)
    {
        for(var i=0; i<rows; i++)
        {
            var cell_id = "cell_" + (i + j * 4);
            var cell = document.getElementById(cell_id);
            var val = board[i][j];

            if(val < 16)
            {
                cell.innerHTML = ("" + val);
                if(val % 2 == 0)
                {
                    cell.className = "cell dark";               
                }
                else
                {
                    cell.className = "cell light";
                }
            }
            else
            {
                cell.innerHTML = "";
                cell.className = "empty";
            }
        }
    }
}

//Event handler for each cell
function cellEventHandle(e)
{
    genPossibleMoves();

    //Current coords of the cell
    var r = e.target.row;
    var c = e.target.col;
    var pos = -1;
    var isPossible = false;
    // console.log(r + "," + c);

    /*
        Check if the current cell is 
        one of the possible moves
    */
    for(var i=0; i<possibleMoves.length; i++)
    {
        if(possibleMoves[i].x == r && possibleMoves[i].y == c)
        {
            isPossible = true;
            pos = i;
            break;
        }
    }

    if(isPossible)
    {
        var temp = possibleMoves[pos];

        //Swap position of the empty cell
        board[zx][zy] = board[temp.x][temp.y];
        //Update the coordinates of the empty cell
        zx = temp.x;
        zy = temp.y;
        board[zx][zy] = 16;
        updateCells();

        //Check if the game is over
        if(is_game_over())
        {
            setTimeout(function(){
                alert("Congrats!");
            }, 2);
        }
    }

}

//Check if the game is over
function is_game_over()
{
    var currentVal = 0;
    for(var j=0; j<cols; j++)
    {
        for(var i=0; i<rows; i++)
        {
            if(board[i][j] < currentVal)
            {
                return false;
            }

            currentVal = board[i][j];
        }
    }
    return true;
}

//Shuffle the board
function shuffleBoard()
{
    var shuffleLimit = 0;
    var temp;

    do
    {
        genPossibleMoves();

        while(true)
        {
            // Pick a random cell of possible moves
            temp = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
            if (temp.x != oldzx || temp.y != oldzy)
            {
                break;
            }
        }

        oldzx = zx;
        oldzy = zy;

        board[zx][zy] = board[temp.x][temp.y];
        zx = temp.x;
        zy = temp.y;
        board[zx][zy] = 16;

    }while(++shuffleLimit < 200);
}

//REstart the game
function restart()
{
    shuffleBoard();
    updateCells();
}

//Start the game
function start()
{
    generateBoard();
    generateCells();
    restart();
}

As I had mentioned above, today's article has nothing to do with Artificial Intelligence but in the future, I plan to write a solver for this game that makes use of Heuristics.

Hope you liked reading this article and have fun playing the game!

Stay tuned for more!

Keep a programming journal using VIM and Bash

Become a better programmer by writing your own programming journal using VIM and Bash.

Back in the days of Polymath scientists, physicists and engineers like Leonardo Da Vinci and Albert Einstein, they usually maintained a sort of journal to record their thoughts, ideas and experiments.

It's been 20 days since I have started maintaining journals, one is for personal stuff and the other is for programming, engineering and math related stuff, I mean, we all need to have some new hobbies to keep ourselves productive, right?

Maintaining a journal helped me create a flow to write down my experiences ofthe day. It also helps me clear my mind and be more emotionally stable (I get moody, sometimes) and record my thoughts and ideas too.

There are so many ways to write a journal like you could sign up on some online platform, install Evernote on your desktop or mobile or traditional pen and paper (which is the best way, honestly).

Requirements

I thought of keeping it in my laptop and I wanted it to have the following features:

  1. No use of internet required
  2. Must be super fast, simple and precise to the point
  3. Privacy (I mean, you can't trust the internet, sometimes!)
  4. Record thoughts and ideas with a timestamp, similar to a logbook
  5. Yes, it must look cool and nerdy

I looked on some options like Google Docs, Dropbox Paper and Evernote but I just wanted something that matches my requirements. I went on YouTube and I saw a guy named Mike Levin, who made a video named "VIM Journalcasting Experiment" and it inspired me to create something like that too.

Setup

First, you need to create a directory to store your journal notes and create a file to create them:

mkdir journal
cd journal
touch writer.sh
chmod u+rwx writer.sh

Next, you need to write a few lines of code in Bash:

#!/bin/bash

folder=`date +%Y_%m_%d`
mkdir -p $folder
cd $folder

vi +star `date +%Y%m%d`".jrnl"

One more step, create an alias on your bash_profile in order to access it from anywhere:

alias jrnl="cd /journal;./writer.sh"

Alright, that's the basic setup! To test it, just do the following in your Terminal:

journal

VIM Customization

Are you one of those people who gets confused on how to get out of VIM? Don't worry, you'll figure it out over here!

The following setup can be done in your ~/.vimrc file to enhance your journaling experience like adding a spellchecker, word counter, highlight colors and so on.

Below are the configurations:

set spell spelllang=en_gb
cmap <F6> setlocal spell!

function! WordCount()
        let s:old_status = v:statusmsg
        let position = getpos(".")
        exe ":silent normal g\<c-g>"
        let stat = v:statusmsg
        let s:word_count = 0
        if stat != '--No lines in buffer--'
                let s:word_count = str2nr(split(v:statusmsg)[11])
                let v:statusmsg = s:old_status
        end
        call setpos('.', position)
        return s:word_count
endfunction

hi User1 ctermbg=black ctermfg=red cterm=BOLD guibg=black guifg=red gui=BOLD

set laststatus=2
set statusline=
set statusline+=%1*
set statusline+=%<\

Now, you can start writing your own journal whenever and wherever you want using VIM.

Conclusion

I'm not saying that you should write your journal on VIM, I thought it would be fun if I could do it on VIM, so that I get a chance to learn it. However, you can do it on Notepad too!

Apart from that, journaling does have a lot of benefits and can help you become more productive!

Get your ideas floating and start building cool stuff!

Hope you liked reading this article!

Until next time, then!