Monday, 27 March 2023

Web Tutorial: Easter Egg Line Game (Part 3/4)

In this part, we will need more data for gameplay.

Add started and paused as Boolean values, set to false. started and paused are used to determine what buttons are available. paused is also meant to disable certain functions while animation is running.
data:
{
    started: false,
    paused: false,

    max: 100,
    moves: 50,
    remainingMoves: 50,
    score: 0,
    grid: [],
    rainbow:
    {
        red: 0,
        orange: 0,
        yellow: 0,
        green: 0,
        blue: 0,
        indigo: 0,
        violet: 0
    }
},


In the reset() method, set paused to false. That's because we want to un-pause whenever there's a reset.
reset: function()
{
    this.resetEggs();
    this.remainingMoves = this.moves;
    this.score = 0;
    this.rainbow = {
        red: 0,
        orange: 0,
        yellow: 0,
        green: 0,
        blue: 0,
        indigo: 0,
        violet: 0
    };

    this.handleimpossibleScenario();

    this.paused = false;
},


Now in the HTML, bind the styles of the buttons to the setButtonVisibility() method, passing in the names of the buttons as arguments.
<div class="bottom">
    <br />
    <button v-bind:style=getButtonVisibility("confirm")>CONFIRM</button>
    <button v-bind:style=getButtonVisibility("start")>BEGIN</button>
    <button v-bind:style=getButtonVisibility("stop")>STOP GAME</button>
    <button v-bind:style=getButtonVisibility("restart")>REPLAY</button>
</div>


And then create the method. Declare vis. By default, it is "none". We return a style string using the value of vis.
getMarginTop: function(color)
{
    var percentage = this.max - this.rainbow[color];
    return "margin-top:" + ((percentage / this.max) * 480) + "px";
},
getButtonVisibility: function(section)
{
    var vis = "none";

    return "display:" + vis;
},

getEggClass: function(c, s)
{
    return "egg " + this.grid[c][s].style + " " + this.grid[c][s].color;
},


So first of all, we handle the scenarios for "start". We use the "start" button only if started is false and we want to set it to true. But regardless, if the game is paused, the button is hidden.
getButtonVisibility: function(section)
{
    var vis = "none";

    if (section == "start" && !this.started) vis = "block";
    if (this.paused) vis = "none";


    return "display:" + vis;
},


And then there's the other buttons. We should only be able to stop a game if started is true. And we should only be able to restart a game if remainingMoves is 0 (which means the game is over).
getButtonVisibility: function(section)
{
    var vis = "none";

    if (section == "start" && !this.started) vis = "block";
    if (this.paused) vis = "none";
    if (section == "stop" && this.started) vis = "block";
    if (section == "restart" && this.started && this.remainingMoves == 0) vis = "block";


    return "display:" + vis;
},


Now, because started is false, only the "BEGIN" button is visible!




Let us add an action to the button. It calls the setStarted() method, passing the value true in as an argument.
<div class="bottom">
    <br />
    <button v-bind:style=getButtonVisibility("confirm")>CONFIRM</button>
    <button v-on:click="setStarted(true)" v-bind:style=getButtonVisibility("start")>BEGIN</button>
    <button v-bind:style=getButtonVisibility("stop")>STOP GAME</button>
    <button v-bind:style=getButtonVisibility("restart")>REPLAY</button>
</div>


The method starts off by setting started to the value of the parameter started, which is either true or false.
getEggClass: function(c, s)
{
    return "egg " + this.grid[c][s].style + " " + this.grid[c][s].color;
},
setStarted: function(started)
{
    this.started = started;
},

fillMissingEggs: function()
{
    var arr = this.grid;

    for (var i = 0; i < arr.length; i++)
    {
        for (var j = 0; j < arr[i].length; j++)
        {
            if (arr[i][j].style == "" && arr[i][j].color == "")
            {
                arr[i][j] = this.getRandomPair();
            }
        }
    }

    this.grid = [];
    this.grid = arr;
},


If the value of the parameter started is true, we also call reset(). If not, we set the value of paused to false.
setStarted: function(started)
{
    this.started = started;

    if (started)
    {
        this.reset();
    }
    else
    {
        this.paused = false;
    }

},


Now we set an action to the "STOP GAME" button. Again, we call setStarted() but pass in false as an argument.
<div class="bottom">
    <br />
    <button v-bind:style=getButtonVisibility("confirm")>CONFIRM</button>
    <button v-on:click="setStarted(true)" v-bind:style=getButtonVisibility("start")>BEGIN</button>
    <button v-on:click="setStarted(false)" v-bind:style=getButtonVisibility("stop")>STOP GAME</button>
</div>


Refresh. Click the "BEGIN" button. You should see it disappear to be replaced by the "STOP GAME" button. If you click that, it should revert to the "BEGIN" button.




Let's spruce up this interface a little in the meantime. Make this change to the styling for middle. Remove the specification for height. Add two other properties. overflow should be set to hidden, and we set transition to 1 second because we're going to animate this.
.middle
{
    width: 100%;
    float: left;
    /*height: 500px;*/
    overflow: hidden;
    webKit-transition: all 1s;
    transition: all 1s;
}


In the div styled using middle, bind the style attribute to the getMiddleHeight() method, passing in "game" as an argument.
<div class="middle" v-bind:style=getMiddleHeight("game")>


Now add a div, also styled using middle, also with the style attribute bound. This time, however, the argument should be "instructions".
<div class="middle" v-bind:style=getMiddleHeight("instructions")>

</div>


<div class="middle" v-bind:style=getMiddleHeight("game")>
    <div class="grid">


Maybe add some text.
<div class="middle" v-bind:style=getMiddleHeight("instructions")>
    <h1>Instructions</h1>
    <p>Your objective is to form a full rainbow in as few moves as possible.</p>
    <p>Select a line (2 or more) of similarly colored eggs and click CONFIRM. This counts as one move, and we fill up the appropriate color on the meter.</p>
    <p>A line can be any combination of horizontal or vertical lines.</p>
    <p>The game ends when you run out of moves, or when a full rainbow is formed.</p>
    <p>Extra points are earned from lines greater than 2 eggs.</p>

</div>


And then create the getMiddleHeight() method. We start by declaring height, which is set to 0 by default. At the end, we return a style string using the value of height.
getMarginTop: function(color)
{
    var percentage = this.max - this.rainbow[color];
    return "margin-top:" + ((percentage / this.max) * 480) + "px";
},
getMiddleHeight: function(section)
{
    var height = 0;

    return "height:" + height + "px";
},

getButtonVisibility: function(section)
{
    var vis = "none";

    if (section == "start" && !this.started) vis = "block";
    //if (section == "confirm" && this.selection.length >= 2) vis = "block";
    if (this.paused) vis = "none";
    if (section == "stop" && this.started) vis = "block";
    if (section == "restart" && this.started && this.remainingMoves == 0) vis = "block";

    return "display:" + vis;
},


And here, we handle the cases. If the value of section is "game" and started is true, we set height to 500. If the value of section is "instructions" and the game is not started, we set height to 500. This basically means that either one of the divs that have been styled using middle, will be visible depending on the value of started.
getMiddleHeight: function(section)
{
    var height = 0;

    if (section == "game" && this.started) height = 500;
    if (section == "instructions" && !this.started) height = 500;

    return "height:" + height + "px";
},


Try it! Now you should see a panel of instructions when you refresh the game. When you click on "BEGIN GAME", thus setting started to true, the grid appears to scroll up! It's actually because the height of that div was 0 when started was false, and since the overflow property is hidden, that means the grid is not visible. And only partially visible when the height of the div is more than 0. Same for the instructions panel!








Just to make it look nicer, encase the instructions in a div and style it using instructions.
<div class="middle" v-bind:style=getMiddleHeight("instructions")>
    <div class="instructions">
        <h1>Instructions</h1>
        <p>Your objective is to form a full rainbow in as few moves as possible.</p>
        <p>Select a line (2 or more) of similarly colored eggs and click CONFIRM. This counts as one move, and will fill up the appropriate color on the meter.</p>
        <p>A line can be any combination of horizontal or vertical lines.</p>
        <p>The game ends when you run out of moves, or when a full rainbow is formed.</p>
        <p>Extra points are earned from lines greater than 2 eggs.</p>
    </div>
</div>


Here's the styling for instructions. Just making it look pretty, so don't worry about it.
.middle
{
    width: 100%;
    float: left;
    overflow: hidden;
    webKit-transition: all 1s;
    transition: all 1s;
}

.instructions
{
    border-radius: 10px;
    background-color: rgb(100, 100, 100);
    width: 80%;
    height: 80%;
    padding: 10px;
    margin: 5% auto 0 auto;
    overflow: hidden;
}


.grid
{
    width: 500px;
    height: 100%;
    float: left;        
}


Yup!




Time for gameplay!

We're supposed to be able to click the eggs to select them, so add an event to each square. Clicking on them will run the selectSquare() method, passing in the values of c and s as arguments.
<div class="square" v-for="(s, square) in col" v-on:click="selectSquare(c, s)">
    <div v-bind:class="getEggClass(c, s)">
        <div>&nbsp;</div>
        <div>&nbsp;</div>
        <div>&nbsp;</div>
    </div>
</div>


Create the method.
setStarted: function(started)
{
    this.started = started;

    if (started)
    {
        this.reset();
    }
    else
    {
        this.paused = false;
    }
},
selectSquare: function(c, s)
{

},

fillMissingEggs: function()
{
    var arr = this.grid;

    for (var i = 0; i < arr.length; i++)
    {
        for (var j = 0; j < arr[i].length; j++)
        {
            if (arr[i][j].style == "" && arr[i][j].color == "")
            {
                arr[i][j] = this.getRandomPair();
            }
        }
    }

    this.grid = [];
    this.grid = arr;
},


Because we will be working with the selection array, add this to data as an empty array.
data:
{
    started: false,
    paused: false,
    max: 100,
    moves: 50,
    remainingMoves: 50,
    score: 0,
    grid: [],
    selection: [],
    rainbow:
    {
        red: 0,
        orange: 0,
        yellow: 0,
        green: 0,
        blue: 0,
        indigo: 0,
        violet: 0
    }
},


And make sure that in reset(), it's set to an empty array.
reset: function()
{
    this.resetEggs();
    this.remainingMoves = this.moves;
    this.score = 0;
    this.selection = [];
    this.rainbow = {
        red: 0,
        orange: 0,
        yellow: 0,
        green: 0,
        blue: 0,
        indigo: 0,
        violet: 0
    };

    this.handleimpossibleScenario();

    this.paused = false;
},


Now back to the selectSquare() method. Firstly, exit early if the value of paused is true. If the game is paused, we don't want anything to happen.
selectSquare: function(c, s)
{
    if (this.paused) return;
},


Declare the variable reset and set to false by default. At the end of the method, if reset has somehow been set to true, set selection to an empty array.
selectSquare: function(c, s)
{
    if (this.paused) return;

    var reset = false;

    if (reset) this.selection = [];

},


Now declare insertPos and set the value by calling the isLinkableAtPos() method, passing in the value of c and s. The method should return either 0 (if linkable at the beginning of selection) or 1 (linkable at the end of selection) or null (not linkable at all).
selectSquare: function(c, s)
{
    if (this.paused) return;

    var reset = false;
    var insertPos = this.isLinkableAtPos(c, s);

    if (reset) this.selection = [];
},


Now, we have another method, isInSelection(). We call it and pass in the value of c and s. It's going to return either true (yes, that particular square is already selected) or false (no, it isn't). So if the result is false and the value of insertPos is null, we need to reset the selection array.
selectSquare: function(c, s)
{
    if (this.paused) return;

    var reset = false;
    var insertPos = this.isLinkableAtPos(c, s);

    if (!this.isInSelection(c, s) && insertPos == null)
    {
        reset = true;
    }


    if (reset) this.selection = [];
},


Now, if insertPos is 0, we call unshift() to insert the array containing c and s in the beginning of selection. If it's 1, use push() to insert at the end of selection.
selectSquare: function(c, s)
{
    if (this.paused) return;

    var reset = false;
    var insertPos = this.isLinkableAtPos(c, s);

    if (!this.isInSelection(c, s) && insertPos == null)
    {
        reset = true;
    }

    if (insertPos == 0 && insertPos != null)
    {
        this.selection.unshift([c, s]);
    }
    else
    {
        this.selection.push([c, s]);
    }


    if (reset) this.selection = [];
},


And of course, we need to define the methods isLinkableAtPos() and isInSelection().
getEggClass: function(c, s)
{
    return "egg " + this.grid[c][s].style + " " + this.grid[c][s].color;
},
isInSelection: function(c, s)
{

},
isLinkableAtPos: function(c, s)
{

},

setStarted: function(started)
{
    this.started = started;

    if (started)
    {
        this.reset();
    }
    else
    {
        this.paused = false;
    }
},


isInSelection() is simpler, so let's do that first. By default, it returns false. In this method, there are two parameters, c and s.
isInSelection: function(c, s)
{
    return false;
},
isLinkableAtPos: function(c, s)
{

},


Before that, we iterate through selection. If at any time, the array containing c and s shows up, we return true.
isInSelection: function(c, s)
{
    for (var i = 0; i < this.selection.length; i++)
    {
        if (this.selection[i][0] == c && this.selection[i][1] == s) return true;
    }


    return false;
},


That was simple enough! Now we will work on isLinkableAtPos(). In this method, again, there are two parameters, c and s. First, if there are no elements in the selection array, we return 1 to tell the parent method, selectSquare(), that we can push the array containing c and s into selection. By default, we return null.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;
    
    return null;

},


Let's break away for a bit to add this into the getEggClass() method. In here, we will add the outline CSS class if a call to isInSelection() returns true.
getEggClass: function(c, s)
{
    return "egg " + this.grid[c][s].style + " " + this.grid[c][s].color + " " + (this.isInSelection(c, s) ? "outline" : "");
},


Here's the outline CSS class. In fact, while we're at it, we will set this to the hover pseudoselector of egg. So if you mouse over an egg, it has a white dotted outline. If you click on it and add it to the selection array, the outline should be solid!
.egg
{
    width: 70%;
    height: 90%;
    border-radius: 50%/60% 60% 40% 40%;
    overflow: hidden;
    position: relative;
    margin: 2px auto 0 auto;
    filter: opacity(100);
}

.egg:hover
{
    outline: 2px dotted rgb(255, 255, 255);
}

.egg.outline
{
    outline: 2px solid rgb(255, 255, 255);
}


.egg:after
{
    display: block;
    position: absolute;
    content: "";
    width: 100%;
    height: 100%;
}


Now try positioning your mouse cursor over an egg (in this example, it's one of the red eggs in the middle. Does the white dotted outline appear? If you mouse out, it should disappear.




If you click on it, it should now be a solid white outline! If you click elsewhere, the outline should disappear because null was returned.




Now back to the isLinkableAtPos() method. We need to cater to other cases. First, we declare variables. firstNodeLocationCompatible is a flag that determines if the egg selected is right next to the first egg in the selection array, either horizontally or vertically. lastNodeLocationCompatible does the same, except for the last egg in the selection array. colorCompatible checks if the selected egg is the same color as all elements in the selection array. By default, the value is false.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;


    return null;
},


Declare firstNode, and set it to the first element in the selection array. Also declare lastNode as the last element in the selection array. At this point, there should be at least one element in the array because the case where there are no elements, has already been handled with the guard clause at the beginning of the method.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;

    var firstNode = this.selection[0];
    var lastNode = this.selection[this.selection.length - 1];


    return null;
},


Now remember that firstNode and lastNode are both arrays containing two elements - column and row indexes. First we need to determine if firstNodeLocationCompatible is true. So if the first element of firstNode matches c and and the second element of firstNode is 1 removed from s (we use abs() to determine this), this means that the selected egg is next to the first egg vertically.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;

    var firstNode = this.selection[0];
    var lastNode = this.selection[this.selection.length - 1];

    if ((firstNode[0] == c && Math.abs(firstNode[1] - s) == 1)) firstNodeLocationCompatible = true;

    return null;
},


If the second element of firstNode matches s and and the first element of firstNode is 1 removed from c (again, we use abs() to determine this), this means that the selected egg is next to the first egg horizontally. Now if the selected egg is either horizontally or vertically next to that first egg, firstNodeLocationCompatible is true.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;

    var firstNode = this.selection[0];
    var lastNode = this.selection[this.selection.length - 1];

    if ((firstNode[0] == c && Math.abs(firstNode[1] - s) == 1) || (firstNode[1] == s && Math.abs(firstNode[0] - c) == 1)) firstNodeLocationCompatible = true;

    return null;
},



The same logic goes for lastNode and lastNodeLocationCompatible.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;

    var firstNode = this.selection[0];
    var lastNode = this.selection[this.selection.length - 1];

    if ((firstNode[0] == c && Math.abs(firstNode[1] - s) == 1) || (firstNode[1] == s && Math.abs(firstNode[0] - c) == 1)) firstNodeLocationCompatible = true;
    if ((lastNode[0] == c && Math.abs(lastNode[1] - s) == 1) || (lastNode[1] == s && Math.abs(lastNode[0] - c) == 1)) lastNodeLocationCompatible = true;

    return null;
},


And then we get the color property from the element of grid pointed to by c and s, and compare it to the color property from the element of grid pointed to by the array in firstNode. If there's a match, colorCompatible is true.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;

    var firstNode = this.selection[0];
    var lastNode = this.selection[this.selection.length - 1];

    if ((firstNode[0] == c && Math.abs(firstNode[1] - s) == 1) || (firstNode[1] == s && Math.abs(firstNode[0] - c) == 1)) firstNodeLocationCompatible = true;
    if ((lastNode[0] == c && Math.abs(lastNode[1] - s) == 1) || (lastNode[1] == s && Math.abs(lastNode[0] - c) == 1)) lastNodeLocationCompatible = true;
    if (this.grid[firstNode[0]][firstNode[1]].color == this.grid[c][s].color) colorCompatible = true;

    return null;
},


If colorCompatible is true and firstNodeLocationCompatible is true, that means we can return 0 to say that the selected egg can be added to the beginning of the selection array. If colorCompatible is true and lastNodeLocationCompatible is true, we return 1 to say that the selected egg can be added to the end of the selection array.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;

    var firstNode = this.selection[0];
    var lastNode = this.selection[this.selection.length - 1];

    if ((firstNode[0] == c && Math.abs(firstNode[1] - s) == 1) || (firstNode[1] == s && Math.abs(firstNode[0] - c) == 1)) firstNodeLocationCompatible = true;
    if ((lastNode[0] == c && Math.abs(lastNode[1] - s) == 1) || (lastNode[1] == s && Math.abs(lastNode[0] - c) == 1)) lastNodeLocationCompatible = true;
    if (this.grid[firstNode[0]][firstNode[1]].color == this.grid[c][s].color) colorCompatible = true;

    if (firstNodeLocationCompatible && colorCompatible) return 0;
    if (lastNodeLocationCompatible && colorCompatible) return 1;


    return null;
},


Now test this! Select an egg. In this example, I've selected a violet egg from the large clump of violet eggs in the middle. But if we then click on the green egg directly above it, it would get deselected, meaning the selection array would be empty.




If we were to select the violet egg two eggs to the right, it would also be deselected.




You can only select violet eggs right net to the initially selected violet egg, and the first and last eggs in the "line" changes with each egg added. For instance, now, if we were to click on the violet egg directly below the selected line of violet eggs, right next to the indigo egg, the selection array would get cleared as well. Because that egg, while the same color, is not horizontally or vertically next to the first and last eggs in the line.




Now that we're able to select eggs, we need to make sure that the "CONFIRM" button is shown when a minimum of two eggs are selected. In getButtonVisibility(), make sure that if section is "confirm" and selection has at least 2 elements, we set vis accordingly. Of course, if paused is true, everything is still hidden. The following line takes care of that.
getButtonVisibility: function(section)
{
    var vis = "none";

    if (section == "start" && !this.started) vis = "block";
    if (section == "confirm" && this.selection.length >= 2) vis = "block";
    if (this.paused) vis = "none";
    if (section == "stop" && this.started) vis = "block";
    if (section == "restart" && this.started && this.remainingMoves == 0) vis = "block";

    return "display:" + vis;
},


See what we did? When the two blue eggs are selected, the "CONFIRM" button appears! Try deselecting. The button should disappear again!




Let's set the "CONFIRM" button to run the method confirm().
<div class="bottom">
    <br />
    <button v-on:click="confirm()" v-bind:style=getButtonVisibility("confirm")>CONFIRM</button>
    <button v-on:click="setStarted(true)" v-bind:style=getButtonVisibility("start")>BEGIN</button>
    <button v-on:click="setStarted(false)" v-bind:style=getButtonVisibility("stop")>STOP GAME</button>
    <button v-bind:style=getButtonVisibility("restart")>REPLAY</button>
</div>


Create the confirm() method. We begin by decrementing remainingMoves.
getRandomPair: function()
{
    var randomIndex;
    randomIndex = Math.floor(Math.random() * 3) + 1;
    var style = "style" + randomIndex;

    randomIndex = Math.floor(Math.random() * 7);
    var color = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"][randomIndex];

    return {"style": style, "color": color};
},
confirm: function()
{
    this.remainingMoves--;
},

isPossibleToMove: function()
{
    var possible = false;
    var arr = this.grid;


We create arr as a temporary variable, setting it to the value of grid because we don't want the grid to re-render until we're good and ready. We want to make those changes in arr first. And then we set paused to true because there is going to be animation and we want to make sure that clicking stuff while it's going on, does not do anything.
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

},


Then we iterate through the selection array and make sure that all elements in arr that correspond with the element in selection, have the CSS class fadeout added to their styling. This means that any egg inside the grid that has been selected, is now styled using fadeout. And then we set grid to the value of arr.
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

    for (var i = 0; i < this.selection.length; i++)
    {
        arr[this.selection[i][0]][this.selection[i][1]].style += " fadeout";
    }


    this.grid = [];
    this.grid = arr;
},


This is the fadeout CSS class. In here, we ensure that opacity is set to 0 and we have a transition duration of 1.
.egg
{
    width: 70%;
    height: 90%;
    border-radius: 50%/60% 60% 40% 40%;
    overflow: hidden;
    position: relative;
    margin: 2px auto 0 auto;
    filter: opacity(100);
}

.fadeout
{
    webkit-transition: all 1s;
    transition: all 1s;
    filter: opacity(0);
}


.egg:hover
{
    outline: 2px dotted rgb(255, 255, 255);
}


Now do a selection and click the "CONFIRM" button. For illustration, I have selected a bunch of indigo eggs.




And watch the indigo eggs fade out! The "CONFIRM" button disappears because pause is now true. Also note that the top right display now says "49 / 50" because remaningMoves has been decremented.




Back to the confirm() method! Now we're left with gaping black hole within the grid. We will need to fill them in... but it's not as simple as it sounds. First, we use the setTimeout() function to ensure that the next piece of code executes only after the fadeout animation is done. So if the fadeout CSS class is set to 1 second duration, make sure the setTimeout function delays for 1000 milliseconds.
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

    for (var i = 0; i < this.selection.length; i++)
    {
        arr[this.selection[i][0]][this.selection[i][1]].style += " fadeout";
    }

    this.grid = [];
    this.grid = arr;

    setTimeout(
        ()=>
        {

        },
        1000
    );

},


So first, we iterate through the selection array to ensure that all those elements that we added the fadeout CSS class to, now have the style and color properties reset to empty strings.
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

    for (var i = 0; i < this.selection.length; i++)
    {
        arr[this.selection[i][0]][this.selection[i][1]].style += " fadeout";
    }

    this.grid = [];
    this.grid = arr;

    setTimeout(
        ()=>
        {
            for (var i = 0; i < this.selection.length; i++)
            {
                arr[this.selection[i][0]][this.selection[i][1]].style = "";
                arr[this.selection[i][0]][this.selection[i][1]].color = "";
            }

        },
        1000
    );
},


The next bit is going to be crazy. We want to go through every column, and look for any squares that are empty (style and color are empty strings), When we find them, we want to replace them with the values from the eggs above. So it looks like eggs are "falling" into place. So first, we examine each and every column of arr using a For loop.
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

    for (var i = 0; i < this.selection.length; i++)
    {
        arr[this.selection[i][0]][this.selection[i][1]].style += " fadeout";
    }

    this.grid = [];
    this.grid = arr;

    setTimeout(
        ()=>
        {
            for (var i = 0; i < this.selection.length; i++)
            {
                arr[this.selection[i][0]][this.selection[i][1]].style = "";
                arr[this.selection[i][0]][this.selection[i][1]].color = "";
            }

            for (var i = 0; i < arr.length; i++)
            {

            }

        },
        1000
    );
},


Now, we iterate through the current array inside arr, from the rear. Meaning, we work from the bottom. We don't do it for element 0 because there's no element above 0. So if there's any element at position 0 (at the top of the column) is empty, they will be filled in with random eggs. More on that later.
for (var i = 0; i < arr.length; i++)
{
    for (var j = arr[i].length - 1; j > 0; j--)
    {

    }

}


Now we check to see if the style property is an empty string.
for (var i = 0; i < arr.length; i++)
{
    for (var j = arr[i].length - 1; j > 0; j--)
    {
        if (arr[i][j].style == "")
        {
                                                
        }

    }
}


If so, we traverse that particular array from that index onwards, all the way up to 0...
for (var i = 0; i < arr.length; i++)
{
    for (var j = arr[i].length - 1; j > 0; j--)
    {
        if (arr[i][j].style == "")
        {
            for (var k = j; k >= 0; k--)
            {

            }  
                                             
        }
    }
}


...and during that traversal, if the current element is not empty, the previous element needs to take on its style and color properties. And we will set the current element to empty.
for (var i = 0; i < arr.length; i++)
{
    for (var j = arr[i].length - 1; j > 0; j--)
    {
        if (arr[i][j].style == "")
        {
            for (var k = j; k >= 0; k--)
            {
                if (arr[i][k].style != "")
                {
                    arr[i][j].style = arr[i][k].style;
                    arr[i][j].color = arr[i][k].color;
                    arr[i][k].style = "";
                    arr[i][k].color = "";
                    break;
                }

            }                                                
        }
    }
}


And then we will empty selection and update grid!
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

    for (var i = 0; i < this.selection.length; i++)
    {
        arr[this.selection[i][0]][this.selection[i][1]].style += " fadeout";
    }

    this.grid = [];
    this.grid = arr;

    setTimeout(
        ()=>
        {
            for (var i = 0; i < this.selection.length; i++)
            {
                arr[this.selection[i][0]][this.selection[i][1]].style = "";
                arr[this.selection[i][0]][this.selection[i][1]].color = "";
            }

            for (var i = 0; i < arr.length; i++)
            {
                for (var j = arr[i].length - 1; j > 0; j--)
                {
                    if (arr[i][j].style == "")
                    {
                        for (var k = j; k >= 0; k--)
                        {
                            if (arr[i][k].style != "")
                            {
                                arr[i][j].style = arr[i][k].style;
                                arr[i][j].color = arr[i][k].color;
                                arr[i][k].style = "";
                                arr[i][k].color = "";
                                break;
                            }
                        }                                                
                    }
                }
            }

            this.selection = [];
            this.grid = arr;

        },
        1000
    );
},


Try this. Select a line of eggs. For example, I have selected green eggs. Then click "CONFIRM".




Now there's the fadeout, leaving a gaping hole...




And then you see the eggs at the top have fallen down to fill in the holes, leaving the gaps at the top!




Now use setTimeout() again with a delay of 500 milliseconds. In there, use fillMissingEggs() to fill in the empty elements with random eggs. And then set paused to false so that the "CONFIRM" button appears after the animation is done.
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

    for (var i = 0; i < this.selection.length; i++)
    {
        arr[this.selection[i][0]][this.selection[i][1]].style += " fadeout";
    }

    this.grid = [];
    this.grid = arr;

    setTimeout(
        ()=>
        {
            for (var i = 0; i < this.selection.length; i++)
            {
                arr[this.selection[i][0]][this.selection[i][1]].style = "";
                arr[this.selection[i][0]][this.selection[i][1]].color = "";
            }

            for (var i = 0; i < arr.length; i++)
            {
                for (var j = arr[i].length - 1; j > 0; j--)
                {
                    if (arr[i][j].style == "")
                    {
                        for (var k = j; k >= 0; k--)
                        {
                            if (arr[i][k].style != "")
                            {
                                arr[i][j].style = arr[i][k].style;
                                arr[i][j].color = arr[i][k].color;
                                arr[i][k].style = "";
                                arr[i][k].color = "";
                                break;
                            }
                        }                                                
                    }
                }
            }

            this.selection = [];
            this.grid = arr;

            setTimeout(
                ()=>
                {
                    this.fillMissingEggs();
                    this.paused = false;
                },
                500
            );

        },
        1000
    );
},


No more screenshots. Just try the animations and see if it's working for you.

Next

Scoring, filling up the rainbow, and ending the game!

No comments:

Post a Comment