Christmas is coming!
As with the yearly tradition, 
TeochewThunder is proud to present the Christmas-themed web tutorial. This year, we will be making a game using VueJS.
Remember those puzzles where the pieces were scrambled and you had to shift those pieces around till you assembled the picture? Yep, that's what we are doing today. For starters, we will use this image. Note that this image is 500 by 500 pixels.
|  | 
| xmas0.jpg | 
We will then cut it up into 16 pieces, scramble the locations of the pieces within a 4 by 4 grid, and let the user click on these pieces to move them into the correct locations.
Let's begin with some HTML. Note that we are including a remote link to the VueJS script.
<!DOCTYPE html>
<html>
    <head>
        <title>Christmas Puzzle</title>
        <style>
        </style>
    </head>
    <body>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.min.js"></script>
        <script>
        </script>
    </body>
</html>
Add a div with id 
xmasApp. This is what VueJS will use to perform binding.
<body>
    <div id="xmasApp">
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.min.js"></script>
    <script>
    </script>
</body>
Style this. Give all divs a 
red outline so we can see what we are doing. 
xmasApp will be 500 pixels wide, and centered in the middle of the screen via the 
margin property. Font size has been set here as well, but that is optional.
<style>
    div { outline: 1px solid #FF0000;}
    #xmasApp
    {
        width: 500px;
        margin: 0 auto 0 auto;
        font-family: verdana;
        font-size: 12px;
    }
</style>
In here, add another div with id of 
labelContainer. Inside this div, add some text and controls for the user. The button will be useful later.
<div id="xmasApp">
    <div id="labelContainer">
        <h1>MERRY CHRISTMAS!</h1>
        <p>
            This picture has been scrambled. Click on the tiles to move them and unscramble the picture. Tip: The top right hand tile is always the blank tile.
        </p>
        <button></button>
    </div>
</div>
Not nearly pretty enough. Let's clean this up.
Here's some styling for 
labelContainer. We set the height and width, and center the text. The button is 
orange, and pretty much everything in the styling is optional.
#xmasApp
{
    width: 500px;
    margin: 0 auto 0 auto;
    font-family: verdana;
    font-size: 12px;
}
#labelContainer
{
    width: 100%;
    height: 150px;
    text-align: center;
}
#labelContainer button
{
    width: 8em;
    height: 2em;
    text-align: center;
    background-color: rgba(255, 100, 0, 1);
    color: rgba(255, 255, 255, 1);
    font-weight: bold;
    border-radius: 10px;
    border: 0px solid black;
}
#labelContainer button:hover
{
    background-color: rgba(255, 100, 0, 0.5);
    color: rgba(255, 0, 0, 1);
}
Much, much better.
Now let's add another div, 
puzzleContainer.
<div id="xmasApp">
    <div id="labelContainer">
        <h1>MERRY CHRISTMAS!</h1>
        <p>
            This picture has been scrambled. Click on the tiles to move them and unscramble the picture. Tip: The top right hand tile is always the blank tile.
        </p>
        <button></button>
    </div>
    <div id="puzzleContainer">
    </div>
</div>
Here with the styling for 
puzzleContainer, you see that the width is the whole of its parent, which is 500 pixels, and its height is also 500 pixels. Yes, it's a square.
#labelContainer button:hover
{
    background-color: rgba(255, 100, 0, 0.5);
    color: rgba(255, 0, 0, 1);
}
#puzzleContainer
{
    width: 100%;
    height: 500px;
}
And here's the square. We will turn it into a grid later on.
Let's do some scripting!
We first instantiate a new 
Vue object. And we pass in an object.
<script>
    var app = new Vue
    (
        {
        }
    );
</script>
The first property we put in, is 
el, the element. This is 
xmasApp. The next properties are 
data and 
methods. Both of these are, in turn, objects. 
created is another property, and it's a method that calls the 
reset() method, which we will define soon.
var app = new Vue
(
    {
        el: "#xmasApp",
        data:
        {
        },
        methods:
        {
        },
        created: function()
        {
            this.reset();
        }                        
    }
);
For 
data, we have the following properties.
btnText: this is a string which will be used as the button label.
pieces: an array that holds information about each piece of the picture.
arrangement: an array that tells us which piece is currently in which slot.
blankIndex: this defines the tile that is blank. 
images: this is a number that defines the number of images we will be using.
imageNo: this is a number which defines which image is in use at the moment.
Below, 
pieces and 
arrangement are empty arrays by default. For 
blankIndex, I personally prefer the top right corner (3) but honestly, any number from 0 to 15 works. Currently we are only using one image, so we set 
images to 1 and we always use the first image by default, so 
imageNo is 0.
data:
{
    btnText: "RESET",
    pieces: [],
    arrangement: [],
    blankIndex: 3,
    images: 1,
    imageNo: 0
},
In methods, we declare 
reset(). We begin by setting the properties of 
data to their default values. For 
imageNo, we set it to a random number from 0 to (images - 1). In this case, since 
images is 1, the result is always 0.
methods:
{
    reset: function()
    {
        this.btnText = "RESET";
        this.pieces = [];
        this.arrangement = [];
        this.blankIndex = 3;
        this.imageNo = Math.floor(Math.random() * this.images);
    }
}
Then we create a nested 
For loop because this is where we define the grid. We have 5 rows and 5 columns.
methods:
{
    reset: function()
    {
        this.btnText = "RESET";
        this.pieces = [];
        this.arrangement = [];
        this.blankIndex = 3;
        this.imageNo = Math.floor(Math.random() * this.images);
        for (var row = 0; row < 4; row++)
        {
            for (var col = 0; col < 4; col++)
            {
            }
        }
    },
}
Here, we declare 
offsetX and 
offsetY so that the browser knows what to offset the image in each piece by. I've done some trial and error, and the optimum number is 33.
methods:
{
    reset: function()
    {
        this.btnText = "RESET";
        this.pieces = [];
        this.arrangement = [];
        this.blankIndex = 3;
        this.imageNo = Math.floor(Math.random() * this.images);
        for (var row = 0; row < 4; row++)
        {
            for (var col = 0; col < 4; col++)
            {
                var offsetX = col * 33;
                var offsetY = row * 33;
            }
        }
    },
}
Then we declare an object, 
piece. The id is a function of 
row and 
col, and we'll have the 
offsetX and 
offsetY properties. And then we will push each 
piece object into the 
pieces array.
methods:
{
    reset: function()
    {
        this.btnText = "RESET";
        this.pieces = [];
        this.arrangement = [];
        this.blankIndex = 3;
        this.imageNo = Math.floor(Math.random() * this.images);
        var tempArr = [];
        for (var row = 0; row < 4; row++)
        {
            for (var col = 0; col < 4; col++)
            {
                var offsetX = col * 33;
                var offsetY = row * 33;
                var piece = 
                {
                    id: (row * 4) + col,
                    offsetX: offsetX,
                    offsetY: offsetY,
                }
                this.pieces.push(piece);                                        
            }
        }
    },
}
Now declare 
holder as an object. The id is also a function of 
row and 
col. If the current 
piece id is equal to 
blankIndex, then the value of 
piece is 
null. Otherwise, it is 
undefined.
And then push the 
piece into 
arrangement.
methods:
{
    reset: function()
    {
        this.btnText = "RESET";
        this.pieces = [];
        this.arrangement = [];
        this.blankIndex = 3;
        this.imageNo = Math.floor(Math.random() * this.images);
        var tempArr = [];
        for (var row = 0; row < 4; row++)
        {
            for (var col = 0; col < 4; col++)
            {
                var offsetX = col * 33;
                var offsetY = row * 33;
                var piece = 
                {
                    id: (row * 4) + col,
                    offsetX: offsetX,
                    offsetY: offsetY,
                }
                this.pieces.push(piece);                                        
                var holder = 
                {
                    id: (row * 4) + col,
                    piece: ((row * 4) + col == this.blankIndex ? null : undefined)
                }
                this.arrangement.push(holder);
            }
        }
    },
}
After that, we iterate through the 
arrangement array. If the index of the current element is not the same as 
blankIndex, set the 
piece property to the corresponding element in the 
pieces array.
reset: function()
{
    this.btnText = "RESET";
    this.pieces = [];
    this.arrangement = [];
    this.blankIndex = 3;
    this.imageNo = Math.floor(Math.random() * this.images);
    var tempArr = [];
    for (var row = 0; row < 4; row++)
    {
        for (var col = 0; col < 4; col++)
        {
            var offsetX = col * 33;
            var offsetY = row * 33;
            var piece = 
            {
                id: (row * 4) + col,
                offsetX: offsetX,
                offsetY: offsetY,
            }
            this.pieces.push(piece);                                    
            var holder = 
            {
                id: (row * 4) + col,
                piece: ((row * 4) + col == this.blankIndex ? null : undefined)
            }
            this.arrangement.push(holder);
        }
    }
    for (var i = 0; i < this.arrangement.length; i++)
    {
        if (i != this.blankIndex) 
        {
            this.arrangement[i].piece = this.pieces[i];
        }
    }
}
Now let's put this code into the HTML. We have the 
pieces array and the 
arrangement array. So for every element in arrangement, we will have a div styled using 
pieceContainer. Within that div will be another div, and its class and style will be bound to the 
getClass() and 
getStyle() methods, respectively. 
<div id="puzzleContainer">
    <div class="pieceContainer" v-for="holder in arrangement">
        <div v-bind:class="getClass(holder.id)" v-bind:style="getStyle(holder.piece)">
        </div>
    </div>
</div>
We'll also bind the 
id attribute to the 
id attribute of the current element. Not really necessary for anything to work, but it's a good practice.
<div id="puzzleContainer">
    <div class="pieceContainer" v-for="holder in arrangement">
        <div v-bind:class="getClass(holder.id)" v-bind:id="'piece'+holder.id" v-bind:style="getStyle(holder.piece)">
        </div>
    </div>
</div>
Here's the styling for 
pieceContainer. Since there are 4 rows and 4 columns of such "pieces", this stands to reason that each of these would take up 25% width and height of the parent. We set the 
float property to 
left so that they fall in line, and set the background color to 
black.
#puzzleContainer
{
    width: 100%;
    height: 500px;
}
.pieceContainer
{
    width: 25%;
    height: 25%;
    float: left;
    background-color: rgba(0, 0, 0, 1);
}
Now let's define the 
getClass() and 
getStyle() methods. Both of these have a parameter, 
id.
    for (var i = 0; i < this.arrangement.length; i++)
    {
        if (i != this.blankIndex) 
        {
            this.arrangement[i].piece = i;
        }
    }
},
getClass: function(id)
{
},
getStyle: function(id)
{
}
For 
getClass(), we first declare 
baseClass. If the id passed in is equal to 
blankIndex, the class is 
blank. But otherwise, it is 
piece. Return the value of 
baseClass, and we're all set for this method, for now.
getClass: function(id)
{
    var baseClass = (id == this.blankIndex ? "blank" : "piece");
    return baseClass;
},
Here's the styling for 
piece and 
blank. Both 
piece and 
blank take up full height and width of the parent, which is 
pieceContainer. But while 
piece has 
background-repeat and 
margin properties (the latter will be relevant later), 
blank only has a 
black background.
.pieceContainer
{
    width: 25%;
    height: 25%;
    float: left;
    background-color: rgba(0, 0, 0, 1);
}
.piece
{
    width: 100%;
    height: 100%;
    background-repeat: no-repeat;
    margin: 0px;
}
.blank
{
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 1);
}
For 
getStyle(), if 
id is 
null, just return an empty string. Because the piece will be styled using 
blank and thus will not require any styling for the background image.
getStyle: function(id)
{
    if (id == null) return "";
}
But if there's a non-null value being passed in, we declare 
obj and set it to the element in 
pieces pointed to by 
id. 
getStyle: function(id)
{
    if (id == null) return "";
    var obj = this.pieces[id];
}
Then we declare 
image and create a styling string value for it, using 
imageNo to set the background image. Remember that right now, the value of 
imageNo is always 0.
getStyle: function(id)
{
    if (id == null) return "";
    var obj = this.pieces[id];
    var image = "background-image: url(xmas" + this.imageNo + ".jpg);";
}
Then declare 
offset. For this, we use the 
offsetX and 
offsetY properties of 
obj. Each piece will thus use the same image background but with a different offset!
getStyle: function(id)
{
    if (id == null) return "";
    var obj = this.pieces[id];
    var image = "background-image: url(xmas" + this.imageNo + ".jpg);";
    var offset = "background-position:" + obj.offsetX + "% " + obj.offsetY + "%";
}
Finally, return both 
image and 
offset in concatenated form.
getStyle: function(id)
{
    if (id == null) return "";
    var obj = this.pieces[id];
    var image = "background-image: url(xmas" + this.imageNo + ".jpg);";
    var offset = "background-position:" + obj.offsetX + "% " + obj.offsetY + "%";
    return image + offset;
}
Also, let's fix this button so that the text appears, and when it gets clicked, it should run the 
reset() method.
<button v-on:click="reset">{{btnText}}</button>
Now, take a look at what we have. It's the picture, divided into 16 pieces, but the top right hand corner piece is blank beause 
blank has a 
black background!
Scrambling the tiles
The tiles are currently in the correct position. We want them to be randomly located. So let's begin by declaring 
tempArr, an empty array.
this.btnText = "RESET";
this.pieces = [];
this.arrangement = [];
this.blankIndex = 3;
this.imageNo = Math.floor(Math.random() * this.images);
var tempArr = [];
We need this array because right after we push the element into 
pieces, we need to push the id into 
tempArr.
var tempArr = [];
for (var row = 0; row < 4; row++)
{
    for (var col = 0; col < 4; col++)
    {
        var offsetX = col * 33;
        var offsetY = row * 33;
        var piece = 
        {
            id: (row * 4) + col,
            offsetX: offsetX,
            offsetY: offsetY,
        }
        this.pieces.push(piece);
        tempArr.push((row * 4) + col);                                        
        var holder = 
        {
            id: (row * 4) + col,
            piece: ((row * 4) + col == this.blankIndex ? null : undefined)
        }
        this.arrangement.push(holder);
    }
}
We're going to remove this line right here.
for (var i = 0; i < this.arrangement.length; i++)
{
    if (i != this.blankIndex) 
    {
        //this.arrangement[i].piece = i;
    }
}
Here, we check if 
tempArr has only one element. That can happen - you'll see why in a minute.
for (var i = 0; i < this.arrangement.length; i++)
{
    if (i != this.blankIndex) 
    {
        //this.arrangement[i].piece = i;
        if (tempArr.length == 1)
        {
        }
        else 
        {
    
        }
    }
}
If there's only one element left in 
tempArr, we just assign the value of that element to the current element of 
arrangement's 
piece property.
for (var i = 0; i < this.arrangement.length; i++)
{
    if (i != this.blankIndex) 
    {
        //this.arrangement[i].piece = i;
        if (tempArr.length == 1)
        {
            this.arrangement[i].piece = tempArr[0];
        }
        else 
        {
    
        }
    }
}
If there's more than one element, then we need to randomly pick one and assign it to the current element of 
arrangement's 
piece property. For this we use a 
Do-while loop that will keep running if the index of the assigned piece is actually 
blankIndex. Eventually, all tiles except 
blankIndex will be randomly assigned.
for (var i = 0; i < this.arrangement.length; i++)
{
    if (i != this.blankIndex) 
    {
        //this.arrangement[i].piece = i;
        if (tempArr.length == 1)
        {
            this.arrangement[i].piece = tempArr[0];
        }
        else 
        {
            do
            {
                                        
            }
            while(this.arrangement[i].piece == this.blankIndex);    
        }
    }
}
Within the loop, declare 
index and set it to a random number between 0 and the length of 
tempArr. Set the 
piece property of the current element of 
arrangement to the element in 
tempArr pointed to by index. And then remove that element from 
tempArr using the 
splice() method. 
Here's some 
info on the 
splice() method.
do
{
    var index = Math.floor(Math.random() * tempArr.length);
    this.arrangement[i].piece = tempArr[index];
    tempArr.splice(index, 1);                                            
}
while(this.arrangement[i].piece == this.blankIndex);
Now refresh your page. The tiles should be randomly arranged, though the top right corner should still be 
black. If you click the button, it should run the 
reset() method and give you a different random arrangement.
We don't want to go into too much at one go, so we'll stop right here.
Next
Making the tiles movable, and setting the win condition.