Tuesday 21 March 2023

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

Hey, hey, hey!

Welcome to 2023's web tutorial for Easter! It's going to be early next month and I will definitely not be able to squeeze out this humongous four-parter in time for that. Therefore, in the tail end of March is when we will et started on this exciting new game!

It involves easter eggs and a rainbow. There are eggs of 7 different colors in a grid, and your task is to join these eggs in a line of the same color. This adds colors to the rainbow, and once you fill up an entire rainbow, the game ends. You also have a limited number of moves to do this with.

To make our task of creating this game easier, we are going to use VueJS for this.

Let's begin!

Here is some HTML. In there, we have a remote link to VueJS. We set the outline of all divs to red for now, just so we can get the layout in order. For the body, we also set background to black and text to white, and the font specs.
<!DOCTYPE html>
<html>
    <head>
        <title>Easter Egg Line Game</title>

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

            body
            {
                background: rgb(0, 0, 0);
                color: rgb(255, 255, 255);
                font-family: Verdana;
                font-size: 15px;
            }
        </style>
    </head>

    <body>
        <div class="container" id="easterApp">

        </div>

        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.min.js"></script>

        <script>

        </script>
    </body>
</html>


The main div is easterApp, and is styled using the container CSS class. We will set VueJS to operate within that div. For the container CSS class, it will be a 700 by 700 pixel square that is set to the middle of the screen using the margin property.
<!DOCTYPE html>
<html>
    <head>
        <title>Easter Egg Line Game</title>

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

            body
            {
                background: rgb(0, 0, 0);
                color: rgb(255, 255, 255);
                font-family: Verdana;
                font-size: 15px;
            }

            .container
            {
                width: 700px;
                height: 700px;
                margin: 0 auto 0 auto;
            }
        </style>
    </head>

    <body>
        <div class="container" id="easterApp">

        </div>

        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.min.js"></script>

        <script>
            var app = new Vue
            (
                {
                    el: "#easterApp",
                    data:
                    {

                    },
                    methods:
                    {

                    }
                }
            );
        </script>
    </body>
</html>


We have three divs with the ids top, middle and bottom.
<div class="container" id="easterApp">
  <div class="top">

  </div>

  <div class="middle">

  </div>

  <div class="bottom">

  </div>

</div>


These are the styles. All of them are set to full width of their parent, container, and float left. They all have different heights. For middle, I have not specified a height. You'll see why later on.
.container
{
  width: 700px;
  height: 700px;
  margin: 0 auto 0 auto;
}

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

.middle
{
  width: 100%;
  float: left;
}

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


Here, the visual should give you a good idea where everything goes. The large rectangle enclosing everything is easterApp, of course. The two thin rectangles you see within easterApp are the ones styled by top and bottom. middle is almost invisible between those two because no height has been set.




Add two divs within the div styled using CSS class top. Their classes will be title and statboard. From these, you should pretty much be able to tell what they contain.
<div class="top">
    <div class="title">

    </div>

    <div class="statboard">

    </div>

</div>


Here's some styling for these classes. We just float one left and one right, and give each one roughly half the width of their parent. Text alignment and such are not that consequential, and in any case, there's no content yet.
.top
{
    width: 100%;
    height: 100px;
    float: left;
}

.title
{
    width: 55%;
    height: 100%;
    float: left;
}

.statboard
{
    width: 35%;
    height: 100%;
    float: right;
    text-align: center;
}


.middle
{
    width: 100%;
    float: left;
}


Placeholders in place!




Now we leave the middle part alone for the time being, and add some buttons to the div that is styled using the bottom CSS class.
<div class="bottom">
    <br />
    <button>CONFIRM</button>
    <button>BEGIN</button>
    <button>STOP GAME</button>
    <button>REPLAY</button>

</div>


I have some styling for the buttons, but it's all aesthetic, and not all that important. Do as you wish.
.bottom
{
    width: 100%;
    height: 100px;
    float: left;
}

.bottom button
{
    width: 10em;
    padding: 5px;
    border-radius: 5px;
    background-color: rgb(100, 100, 100);
    margin: 0 auto 1em auto;
}

.bottom button:hover
{
    background-color: rgb(200, 200, 200);
}


Not all these buttons are going to be visible at the same time. But for now, let's put all these in here.




Now for the middle! Add two divs in there, styled using the grid and rainbow CSS classes.
<div class="middle">
    <div class="grid">

    </div>

    <div class="rainbow">

    </div>

</div>


grid has a fixed width, and takes up 100% of its parent's height, and floats left. rainbow has a fixed height and width, is floated right, and I've imposed a little bit of a margin on the top and right.
.middle
{
    width: 100%;
    float: left;
}

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

.rainbow
{
    width: 180px;
    height: 480px;
    margin-top: 10px;
    margin-right: 10px;
    float: right;            
}


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


You can see the div styled using rainbow. The div styled using grid is not visible because its parent does not have a defined height. And since grid's height is 100% of its parent and it has no content, it also has no defined height and therefore defaults to 0 pixels in height. We'll get back to that!




Let's add some data to the data object. We will start with simple ones. moves is the total number of moves a game is allowed. The game starts with this number of moves. remainingMoves is set to the value of moves at the beginning of a game, and decremented with each move. Once remainingMoves reaches 0, the game is over. score starts at 0, and will increase as the game goes on.
data:
{
    moves: 50,
    remainingMoves: 50,
    score: 0

},


Now let's see what the data will look like when we add binding to the HTML.
<div class="statboard">
    <b>Score</b>
    <br />
    {{ score }}
    <br /><br />
    <b>Moves Remaining</b>
    <br />
    {{ remainingMoves }} / {{ moves }}

</div>


Great!




Now for more complicated data. We want to add an array, grid.
data:
{
    moves: 50,
    remainingMoves: 50,
    score: 0,
    grid: []

},


Before we work on that, create the reset() method in the methods object. We begin by setting remainingMoves to the value of moves. And setting score to 0.
methods:
{
    reset: function()
    {
        this.remainingMoves = this.moves;
        this.score = 0;
    }

}


And calling the resetEggs() method.
methods:
{
    reset: function()
    {
        this.resetEggs();
        this.remainingMoves = this.moves;
        this.score = 0;
    }
}


Now create the method. In there, we begin by setting grid back to an empty array.
methods:
{
    reset: function()
    {
        this.resetEggs();
        this.remainingMoves = this.moves;
        this.score = 0;
    },
    resetEggs: function()
    {
        this.grid = [];
    }

}


The first element is an array of ten objects. Each object has two properties, style and color. All values are empty strings.
resetEggs: function()
{
    this.grid = [];
    this.grid = [
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}]
    ];

}


Repeat this 9 more times. This effectively gives you a 10 by 10 element array.
resetEggs: function()
{
    this.grid = [];
    this.grid = [
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}]

    ];
}


Finally, add this line in the created method. In it, we run reset().
created: function()
{
    this.reset();
}


This is still not going to do anything, at least visibly, until we put this in the DOM and style it. So let's do that. Use the v-for directive to iterate through grid, producing a div styled using the col CSS class for each element in grid.
<div class="grid">
    <div class="col" v-for="(c, col) in grid">

    </div>

</div>


Here is the styling for col. We want it to be only 50 pixels wide, but take up 100% of its parent's height, and float left.
.rainbow
{
    width: 180px;
    height: 480px;
    margin-top: 10px;
    margin-right: 10px;
    float: right;            
}

.col
{
    width: 50px;
    height: 100%;
    float: left;
}


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


And for the time being, we will set the height of middle to 500px.
.middle
{
    width: 100%;
    float: left;
    height: 500px;
}


And here you see the magic happen!




Now, for each element in each element of grid, we want to produce a div, styled using the square CSS class. So, you guessed, it, we use a nested v-for directive.
<div class="grid">
    <div class="col" v-for="(c, col) in grid">
        <div class="square" v-for="(s, square) in col">

        </div>

    </div>
</div>


And style square accordingly. Height and width are both 50 pixels. We float this div left, though it's not strictly necessary. The cursor property is set to pointer because we want this to be clickable, and visually it's an indicator.
.col
{
    width: 50px;
    height: 100%;
    float: left;
}

.square
{
    width: 50px;
    height: 50px;
    float: left;
    cursor: pointer;
}


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


Good. Very good.




Now let's populate the rainbow. Add this array in the data object. It is the object, rainbow. In it is seven properties, named after the colors of the rainbow. All of them default to 0.
data:
{
    moves: 50,
    remainingMoves: 50,
    score: 0,
    grid: [],
    rainbow:
    {
        red: 0,
        orange: 0,
        yellow: 0,
        green: 0,
        blue: 0,
        indigo: 0,
        violet: 0
    }

},


Now in the div styled using rainbow, create 7 divs. They will be styled using the CSS class stripe.
<div class="rainbow">
    <div class="stripe"></div>
    <div class="stripe"></div>
    <div class="stripe"></div>
    <div class="stripe"></div>
    <div class="stripe"></div>
    <div class="stripe"></div>
    <div class="stripe"></div>

</div>


Here is the styling for stripe. The width is set narrow at 18 pixels, with a 100% height to match the height of its parent, rainbow. They will be floated right to fill out the entire container. The overflow property is set to hidden, and you will see why soon.
.rainbow
{
    width: 180px;
    height: 480px;
    margin-top: 10px;
    margin-right: 10px;
    float: right;            
}

.stripe
{
    width: 18px;
    height: 100%;
    float: right;
    overflow: hidden;
}


.col
{
    width: 50px;
    height: 100%;
    float: left;
}


You see the "stripes"?




Now add more classes for each div. These classes will be named after the colors of the rainbow, with the prefix "outline_".
<div class="stripe outline_red"></div>
<div class="stripe outline_orange"></div>
<div class="stripe outline_yellow"></div>
<div class="stripe outline_green"></div>
<div class="stripe outline_blue"></div>
<div class="stripe outline_indigo"></div>
<div class="stripe outline_violet"></div>


Here are the stylings for these classes. Each class has a 1 pixel border and a background color. The color is the same, except that the background color is translucent.
.bottom button:hover
{
    background-color: rgb(200, 200, 200);
}

.outline_red
{
    border: 1px solid rgb(150, 0, 0);
    background-color: rgba(150, 0, 0, 0.2);
}

.outline_orange
{
    border: 1px solid rgb(200, 150, 0);
    background-color: rgba(200, 150, 0, 0.2);
}

.outline_yellow
{
    border: 1px solid rgb(200, 200, 0);
    background-color: rgba(200, 200, 0, 0.2);
}

.outline_green
{
    border: 1px solid rgb(0, 150, 0);
    background-color: rgba(0, 150, 0, 0.2);
}

.outline_blue
{
    border: 1px solid rgb(0, 0, 150);
    background-color: rgba(0, 0, 150, 0.2);
}

.outline_indigo
{
    border: 1px solid rgb(100, 0, 200);
    background-color: rgba(100, 0, 200, 0.2);
}

.outline_violet
{
    border: 1px solid rgb(200, 50, 200);
    background-color: rgba(200, 50, 200, 0.2);
}


So far so good.




Now add a div inside each div. Each has two CSS classes -  stripe_fill and a class named after a color of the rainbow.
<div class="rainbow">
    <div class="stripe outline_red"><div class="stripe_fill red"></div></div>
    <div class="stripe outline_orange"><div class="stripe_fill orange"></div></div>
    <div class="stripe outline_yellow"><div class="stripe_fill yellow"></div></div>
    <div class="stripe outline_green"><div class="stripe_fill green"></div></div>
    <div class="stripe outline_blue"><div class="stripe_fill blue"></div></div>
    <div class="stripe outline_indigo"><div class="stripe_fill indigo"></div></div>
    <div class="stripe outline_violet"><div class="stripe_fill violet"></div></div>
</div>


For stripe_fill, we want height and width to be 100% of its parent, and we want a 1 second duration for animation.
.stripe
{
    width: 18px;
    height: 100%;
    float: right;
    overflow: hidden;
}

.stripe_fill
{
    width: 100%;
    height: 100%;
    webkit-transition: all 1s;
    transition: all 1s;
}


.col
{
    width: 50px;
    height: 100%;
    float: left;
}


For the colors, this is what we'll do.
.outline_red
{
    border: 1px solid rgb(150, 0, 0);
    background-color: rgba(150, 0, 0, 0.2);
}

.red
{
    background-color: rgb(150, 0, 0);
}


.outline_orange
{
    border: 1px solid rgb(200, 150, 0);
    background-color: rgba(200, 150, 0, 0.2);
}

.orange
{
    background-color: rgb(200, 150, 0);
}


.outline_yellow
{
    border: 1px solid rgb(200, 200, 0);
    background-color: rgba(200, 200, 0, 0.2);
}

.yellow
{
    background-color: rgb(200, 200, 0);
}


.outline_green
{
    border: 1px solid rgb(0, 150, 0);
    background-color: rgba(0, 150, 0, 0.2);
}

.green
{
    background-color: rgb(0, 150, 0);
}


.outline_blue
{
    border: 1px solid rgb(0, 0, 150);
    background-color: rgba(0, 0, 150, 0.2);
}

.blue
{
    background-color: rgb(0, 0, 150);
}


.outline_indigo
{
    border: 1px solid rgb(100, 0, 200);
    background-color: rgba(100, 0, 200, 0.2);
}

.indigo
{
    background-color: rgb(100, 0, 200);
}


.outline_violet
{
    border: 1px solid rgb(200, 50, 200);
    background-color: rgba(200, 50, 200, 0.2);
}

.violet
{
    background-color: rgb(200, 50, 200);
}


Nice!




Add this binding to each div. The style attribute is bound to the output of the getMarginTop() method, with the color passed in as a string.
<div class="rainbow">
    <div class="stripe outline_red"><div class="stripe_fill red" v-bind:style=getMarginTop("red")></div></div>
    <div class="stripe outline_orange"><div class="stripe_fill orange" v-bind:style=getMarginTop("orange")></div></div>
    <div class="stripe outline_yellow"><div class="stripe_fill yellow" v-bind:style=getMarginTop("yellow")></div></div>
    <div class="stripe outline_green"><div class="stripe_fill green" v-bind:style=getMarginTop("green")></div></div>
    <div class="stripe outline_blue"><div class="stripe_fill blue" v-bind:style=getMarginTop("blue")></div></div>
    <div class="stripe outline_indigo"><div class="stripe_fill indigo" v-bind:style=getMarginTop("indigo")></div></div>
    <div class="stripe outline_violet"><div class="stripe_fill violet" v-bind:style=getMarginTop("violet")></div></div>
</div>


Create this method. color is the parameter in this method.
resetEggs: function()
{
    this.grid=[];
    this.grid = [
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}],
        [{style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}, {style: "", color: ""}]
    ];
},
getMarginTop: function(color)
{

}


Before proceeding further, add the property max to the data object. This is the number needed to fill each "stripe" up totally. We'll set it at 100.
data:
{
    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 getMarginTop(), we begin by declaring the variable percentage. It is defined as the value of max, less the value of the property of the rainbow object pointed to by color. And finally, we return the margin-top style specification by first dividing percentage by max, then multiplying it by 480 (which is the height of each "stripe".
getMarginTop: function(color)
{
    var percentage = this.max - this.rainbow[color];
    return "margin-top:" + ((percentage / this.max) * 480) + "px";

},


Now, we test this in the reset() method. Just change those numbers to any value between 0 and 100.
reset: function()
{
    this.resetEggs();
    this.remainingMoves = this.moves;
    this.score = 0;
    this.rainbow = {
        red: 0,
        orange: 10,
        yellow: 25,
        green: 50,
        blue: 70,
        indigo: 20,
        violet: 5
    };
},


Oooh. You can see the red "stripe" is empty. Other colored "stripes" are partially filled according to how close they are to the value of max, which is 100. Thus the blue "stripe" is the most filled one.




Undo the changes. We want them to start at 0.
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
    };
},


And we're done for now. There's still a whole lot to do...

Next

Eggs, eggs and more eggs!

No comments:

Post a Comment