Friday, 5 February 2016

Web Tutorial: Valentine's Maze Game (Part 1/4)

Valentine's Day approaches in a week! Romance is in the air. The earth shakes with passion.

OK, enough with the corny cliches. This Valentine's Day special web tutorial will be a long one.

From my review of the mobile game Agent Alice, there was a mini-game in it I was very fond of. And I decided I simply had to make my own.



After that, I gave it a Valentine's Day theme, and voila!

It's a game where all the pieces of a maze-like puzzle are rotated. You have to rotate the correct pieces to connect from Point A to Point B. This tutorial will be JavaScript-heavy. Hold on to your hats!

I'm going to start off with very minimal HTML.

<html>
    <head>
        <title>Find</title>

        <style>
            #board_wrapper
            {
                width:602px;
                height:402px;
                margin:50px auto 0 auto;
                border:2px solid #000000;
            }

            .text_wrapper
            {
                width:100%;
                height:auto;
                min-height:30px;
                text-align:center;
                color:#FF4400;
                font-size:14px;
            }
        </style>
    </head>
    <body>
        <div class="text_wrapper">
            Difficulty:
            <select id="ddlDifficulty">
                <option value="" selected>Easy</option>
            </select>
       

        <div id="pnlMessage" class="text_wrapper">
        <div id="pnlTimer" class="text_wrapper">Time left: 0 seconds
        <div id="board_wrapper">

        </div>    </body>
</html>

The CSS class text_wrapper is purely cosmetic - positioning, look and feel, do what you want with it. Go crazy. But the CSS class board_wrapper is fairly important. Here's what it does.

- border:2px solid #000000 sets the border
- width:602px, height:402px is like this for a very specific reason. The board is supposed to be made up of 4 rows, each containing 6 tiles. Each tile is 100x100 pixels. The extra 2 pixels leaves allowance for the border.
- margin:50px auto 0 auto puts the board smack in the middle of the screen.
               

This is what you should have right now.




You notice that the drop down list ddlDifficulty has only one option in it. This is deliberate and temporary.

The Game Tiles

OK, we've created the board. It's time to set up game tiles.

Each tile is a div that will be styled using this class. As earlier advertised, the height is 100 pixels and so is the width. The position property is set to absolute because each of these tiles needs to rotate on its own without affecting (visually, at least) the others. Lastly, the cursor property is set to pointer, so users know that these tiles should be clicked on.

I've created the following image to serve as the background image for these tiles.
easy.png

Modify your HTML, like so. This adds a value incorporating the URL of this image. By default, this option is selected.

            <select id="ddlDifficulty">
                <option value="url(easy.png)" selected>Easy</option>
            </select>


Now, we're going to dive right into the JavaScript. This could get a little hairy.

Add this code into your head tag.
    <head>
        <title>Find</title>

        <style>
            #board_wrapper
            {
                width:602px;
                height:402px;
                margin:50px auto 0 auto;
                border:2px solid #000000;
            }

            .text_wrapper
            {
                width:100%;
                height:auto;
                min-height:30px;
                text-align:center;
                color:#FF4400;
                font-size:14px;
            }
        </style>

        <script>
            var objtiletype=[];
            objtiletype[0] = {pos:"0px 0px", n:0, e:1, s:1, w:0};
            objtiletype[1] = {pos:"-200px 0px", n:0, e:0, s:1, w:1};
            objtiletype[2] = {pos:"-200px -200px", n:1, e:0, s:0, w:1};
            objtiletype[3] = {pos:"0px -200px", n:1, e:1, s:0, w:0};
            objtiletype[4] = {pos:"-100px 0px", n:0, e:1, s:0, w:1};
            objtiletype[5] = {pos:"0px -100px", n:1, e:0, s:1, w:0};
            objtiletype[6] = {pos:"-100px -200px", n:0, e:1, s:0, w:1};
            objtiletype[7] = {pos:"-200px -100px", n:1, e:0, s:1, w:0};
            objtiletype[8] = {pos:"-52px -100px", n:0, e:1, s:0, w:0};
            objtiletype[9] = {pos:"-148px -100px", n:0, e:0, s:0, w:1}; 

            var objtiles=[];
        </script>
    </head>

objtile is a global array meant to represent your game tiles.

objtiletype is a global array of objects. Each objtiletype has a x and y pixel offset defined by its property pos. n, s, e and w correspond to the cardinal points north, south, east and west. There are 10 types of tiles - the last 2 are start and end tiles, while the first 8 are normal game tile types. And if each objtiletype had the image I created as a background, that's what they would look like.

Normal game tiles.
Start and end game tiles.

Example - objtiletype[1]'s n and e values are 0, while s and w are 1. Do you see that in the tile (top left corner), the only exit/entry points are at the south and west? That's what the cardinal points are for.

Don't worry too much about this part; I'll get back to this soon.

Modify your HTML.

    <body onload="resetboard();">
        <div class="text_wrapper">
            Difficulty:
            <select id="ddlDifficulty">
                <option value="url(easy.png)" selected>Easy</option>
            </select>
       

        <div id="pnlMessage" class="text_wrapper">
        <div id="pnlTimer" class="text_wrapper">Time left: 0 seconds
        <div id="board_wrapper">

        </div>    </body>

Create the resetboard() function.

        <script>
            var objtiletype=[];
            objtiletype[0] = {pos:"0px 0px", n:0, e:1, s:1, w:0};
            objtiletype[1] = {pos:"-200px 0px", n:0, e:0, s:1, w:1};
            objtiletype[2] = {pos:"-200px -200px", n:1, e:0, s:0, w:1};
            objtiletype[3] = {pos:"0px -200px", n:1, e:1, s:0, w:0};
            objtiletype[4] = {pos:"-100px 0px", n:0, e:1, s:0, w:1};
            objtiletype[5] = {pos:"0px -100px", n:1, e:0, s:1, w:0};
            objtiletype[6] = {pos:"-100px -200px", n:0, e:1, s:0, w:1};
            objtiletype[7] = {pos:"-200px -100px", n:1, e:0, s:1, w:0};
            objtiletype[8] = {pos:"-52px -100px", n:0, e:1, s:0, w:0};
            objtiletype[9] = {pos:"-148px -100px", n:0, e:0, s:0, w:1}; 

            var objtiles=[];

            function resetboard()
            {
                var board=document.getElementById("board_wrapper");
                board.innerHTML="";
               
                var rowcounter=0,colcounter=0;
                var tile,tiletypeindex;

                for (var i=0;i<24;i++)
                {

                }
            }
        </script>


The first two lines clears the game board. Next, the variables rowcounter and colcounter are declared, along with tile and tiletypeindex.

Next is the For loop. There are a total of (4x6=24) tiles. So tiles is an array of elements indexed from 0 to 24. This For loop initializes the values of each objtile and places them on the board according to their number.

Add this to your resetboard() function.

            function resetboard()
            {
                var board=document.getElementById("board_wrapper");
                board.innerHTML="";
               
                var rowcounter=0,colcounter=0;
                var tile,tiletypeindex;

                for (var i=0;i<24;i++)
                {
                    tiletypeindex=generaterandomno(0,7);
                    objtiles[i] = {id:i, x:colcounter, y:rowcounter, n:objtiletype[tiletypeindex].n,s:objtiletype[tiletypeindex].s,e:objtiletype[tiletypeindex].e,w:objtiletype[tiletypeindex].w};
                }
            }

            function generaterandomno(varmin,varmax)
            {
                return Math.floor((Math.random() * (varmax-varmin+1)) + varmin);
            }


tiletypeindex is determined by generating a number from 0 to 7. Sure, there are 10 tile types as shown by the array objtiletype, but remember only the first 8, ie elements 0 to 7, are normal game tiles. So each tile type for each objtile, is determined randomly. Its n, s, e and w values will then correspond to those values in the objtiletype. This will be important later!

Add this to your resetboard() function.

                for (var i=0;i<24;i++)
                {
                    tiletypeindex=generaterandomno(0,7);
                    objtiles[i] = {id:i, x:colcounter, y:rowcounter, n:objtiletype[tiletypeindex].n,s:objtiletype[tiletypeindex].s,e:objtiletype[tiletypeindex].e,w:objtiletype[tiletypeindex].w};

                    tile=document.createElement("div");
                    tile.id="tile"+i;

                    tile.className="tile_wrapper";
                    tile.style.marginLeft=objtiles[i].x*100+"px";
                    tile.style.marginTop=objtiles[i].y*100+"px";

                    if (colcounter % 5==0&&colcounter>0)
                    {
                        rowcounter++;
                        colcounter=0;
                    }
                    else
                    {
                        colcounter++;
                    }
                }


The role of the rowcounter and colcounter variables becomes clearer now. Each row is only supposed to have 6 tiles. colcounter is incremented with each iteration of the For loop. But once colcounter reaches a number divisible by 5, rowcounter is incremented and colcounter is reset to 0.  

rowcounter multiplied by 100 is the number of pixels each objtile has its margin-top property offset by.  

colcounter multiplied by 100 is the number of pixels each objtile has its margin-top property offset by.

Also, the class of the tile is set to tile_wrapper. So we'll need to create that class in your CSS.

        <style>
            #board_wrapper
            {
                width:602px;
                height:402px;
                margin:50px auto 0 auto;
                border:2px solid #000000;
            }

            .tile_wrapper
            {
                width:100px;
                height:100px;
                position:absolute;
                cursor:pointer;
            }

            .text_wrapper
            {
                width:100%;
                height:auto;
                min-height:30px;
                text-align:center;
                color:#FF4400;
                font-size:14px;
            }
        </style>


I'm numbering the tiles to show you where each tile belongs, along with its rowcounter and colcounter values.


Tile 0
rowcounter = 0
colcounter = 0


Tile 1
rowcounter = 0
colcounter = 1


Tile 2
rowcounter = 0
colcounter = 2


Tile 3
rowcounter = 0
colcounter = 3


Tile 4
rowcounter = 0
colcounter = 4


Tile 5
rowcounter = 0
colcounter = 5


Tile 6
rowcounter = 1
colcounter = 0


Tile 7
rowcounter = 1
colcounter = 1


Tile 8
rowcounter = 1
colcounter = 2


Tile 9
rowcounter = 1
colcounter = 3


Tile 10
rowcounter = 1
colcounter = 4


Tile 11
rowcounter = 1
colcounter = 5


Tile 12
rowcounter = 2
colcounter = 0


Tile 13
rowcounter = 2
colcounter = 1


Tile 14
rowcounter = 2
colcounter = 2


Tile 15
rowcounter = 2
colcounter = 3


Tile 16
rowcounter = 2
colcounter = 4


Tile 17
rowcounter = 2
colcounter = 5


Tile 18
rowcounter = 3
colcounter = 0


Tile 19
rowcounter = 3
colcounter = 1


Tile 20
rowcounter = 3
colcounter = 2


Tile 21
rowcounter = 3
colcounter = 3


Tile 22
rowcounter = 3
colcounter = 4


Tile 23
rowcounter = 3
colcounter = 5

Add this to your resetboard() function.

                for (var i=0;i<24;i++)
                {
                    tiletypeindex=generaterandomno(0,7);
                    objtiles[i] = {id:i, x:colcounter, y:rowcounter, n:objtiletype[tiletypeindex].n,s:objtiletype[tiletypeindex].s,e:objtiletype[tiletypeindex].e,w:objtiletype[tiletypeindex].w};

                    tile=document.createElement("div");
                    tile.id="tile"+i;

                    tile.className="tile_wrapper";
                    tile.style.marginLeft=objtiles[i].x*100+"px";
                    tile.style.marginTop=objtiles[i].y*100+"px";
                    tile.style.backgroundImage=document.getElementById("ddlDifficulty").value;
                    tile.style.backgroundPosition=objtiletype[tiletypeindex].pos;
                    tile.style.backgroundRepeat="no-repeat";
                    tile.style.transform="rotate(0deg)";
                    tile.style.webkitTransform="rotate(0deg)";

                    if (colcounter % 5==0&&colcounter>0)
                    {
                        rowcounter++;
                        colcounter=0;
                    }
                    else
                    {
                        colcounter++;
                    }
                }


Now each tile uses the value of the ddlDifficulty drop-down list as its background image. The image is then offset by the x and y values determined by its corresponding objtiletype.pos property. Now we set a few more properties, such as backgroundRepeat and rotate, and we're good.

Add this to your resetboard() function.

                for (var i=0;i<24;i++)
                {
                    tiletypeindex=generaterandomno(0,7);
                    objtiles[i] = {id:i, x:colcounter, y:rowcounter, n:objtiletype[tiletypeindex].n,s:objtiletype[tiletypeindex].s,e:objtiletype[tiletypeindex].e,w:objtiletype[tiletypeindex].w};

                    tile=document.createElement("div");
                    tile.id="tile"+i;

                    tile.className="tile_wrapper";
                    tile.style.marginLeft=objtiles[i].x*100+"px";
                    tile.style.marginTop=objtiles[i].y*100+"px";
                    tile.style.backgroundImage=document.getElementById("ddlDifficulty").value;
                    tile.style.backgroundPosition=objtiletype[tiletypeindex].pos;
                    tile.style.backgroundRepeat="no-repeat";
                    tile.style.transform="rotate(0deg)";
                    tile.style.webkitTransform="rotate(0deg)";
                    board.appendChild(tile);

                    if (colcounter % 5==0&&colcounter>0)
                    {
                        rowcounter++;
                        colcounter=0;
                    }
                    else
                    {
                        colcounter++;
                    }
                }


This now appends the tile object you created, into the game board, at the exact placement.

Refresh your code, and the page should look like this now.



Next

There's more of course. We're just getting started. Game logic is next on the menu!

No comments:

Post a Comment