Tuesday 9 February 2016

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

Still with me? Cool!

In order to win the game, the player needs to click on the tiles to rotate them till there is a clear unbroken line from the start tile to the end tile.


We'll write the code for rotating those tiles.

First, go to your resetboard() function and add these lines. Upon initialization of each tile, the rotatetile() function will be added to the onclick event of that tile.

            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};

                    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)";
                    tile.onclick=function(){rotatetile(this,0.3);};
                    board.appendChild(tile);

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


Add this line after the objtiletype array definition. This defines the global variable started, which is false by default.

            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=[];
            var path=[];
            var started=false;


Now create the rotatefile() function. The If-else statement here gives you control over the rotation. If the game is not started, clicking only brings up a prompt asking the player to start the game.

            function rotatetile(vartile,varsec)
            {
                if (started)
                {
       
                }
                else
                {
                    alert("Please start the game first.");
                    return false;
                }
            }

            function resetboard()
            {


If the game has been started, the two arguments passed into the rotatetile() function come into play. vartile is the id of the tile that was clicked, while varsec defines the number of seconds it takes for the rotation to complete. Adjust varsec as you see fit! Now, the code gets the rotation status of the tile, adds 90 degrees to it, then uses varsec to determine the speed of the rotation.

            function rotatetile(vartile,varsec)
            {
                if (started)
                {
                    var timer=document.getElementById("pnlTimer");
                    var seconds=timer.innerHTML;
                    var degrees=vartile.style.transform;
                    degrees=degrees.replace("rotate(","");
                    degrees=degrees.replace("deg)","");
                    degrees=parseInt(degrees)+90;
                    vartile.style.transition="all "+varsec+"s";
                    vartile.style.transform="rotate("+degrees+"deg)";    
                    vartile.style.webkitTransition="all "+varsec+"s";
                    vartile.style.webkitTransform="rotate("+degrees+"deg)";   
                }
                else
                {
                    alert("Please start the game first.");
                    return false;
                }
            }


But that's just the visual rotation. We have to take care of the logical part too. The n, s, e and w values of the tile will change with each rotation.

            function rotatetile(vartile,varsec)
            {
                if (started)
                {
                    var timer=document.getElementById("pnlTimer");
                    var seconds=timer.innerHTML;
                    var degrees=vartile.style.transform;
                    degrees=degrees.replace("rotate(","");
                    degrees=degrees.replace("deg)","");
                    degrees=parseInt(degrees)+90;
                    vartile.style.transition="all "+varsec+"s";
                    vartile.style.transform="rotate("+degrees+"deg)";
                    vartile.style.webkitTransition="all "+varsec+"s";
                    vartile.style.webkitTransform="rotate("+degrees+"deg)";    

                    var id=parseInt(vartile.id.replace("tile",""));
                    var n,e,s,w;
                    n=objtiles[id].w;
                    e=objtiles[id].n;
                    s=objtiles[id].e;
                    w=objtiles[id].s;

                    objtiles[id].n=n;

                    objtiles[id].e=e;
                    objtiles[id].s=s;
                    objtiles[id].w=w;           
                }
                else
                {
                    alert("Please start the game first.");
                    return false;
                }
            }


Just a visual to help you see what I mean. Let's say this tile is rotated...




Before


After
n 0 0
s 1 1
e 1 0
w 0 1


Or this tile.




Before


After
n 1 0
s 1 0
e 0 1
w 0 1


Try it! Run your code. Click on the tiles. What happens? Bet you just kept getting prompts to start the game, eh? No sweat, there's something you're missing. Alter your reset() function. So clicking on "begin" not only resets the board, it also starts the time with the timer_start() function!

            function reset()
            {
                var start_id,end_id;
                var tiletypeindex;

                do
                {
                    resetboard();
                    start_id=generaterandomno(0,3) * 6;
                    objtiles[start_id].n=objtiletype[8].n;
                    objtiles[start_id].s=objtiletype[8].s;
                    objtiles[start_id].e=objtiletype[8].e;
                    objtiles[start_id].w=objtiletype[8].w;
                    end_id=(generaterandomno(1,4) * 6) - 1;
                    objtiles[end_id].n=objtiletype[9].n;
                    objtiles[end_id].s=objtiletype[9].s;
                    objtiles[end_id].e=objtiletype[9].e;
                    objtiles[end_id].w=objtiletype[9].w;

                    document.getElementById("tile"+start_id).style.backgroundPosition=objtiletype[8].pos;
                    document.getElementById("tile"+end_id).style.backgroundPosition=objtiletype[9].pos;

                    createpath(start_id,end_id);
                }
                while (path[path.length-1]==-1);

                //illustrate final path
                for (var i=1;i<path.length-1;i++)
                {
                    tiletypeindex=generaterandomno(0,3);

                    if (objtiles[path[i-1]].x==objtiles[path[i]].x&&objtiles[path[i+1]].x==objtiles[path[i]].x)
                    {
                        tiletypeindex=generaterandomno(4,7);
                    }

                    if (objtiles[path[i-1]].y==objtiles[path[i]].y&&objtiles[path[i+1]].y==objtiles[path[i]].y)
                    {
                        tiletypeindex=generaterandomno(4,7);
                    }

                      document.getElementById("tile"+path[i]).style.backgroundPosition=objtiletype[tiletypeindex].pos;

                    objtiles[path[i]].n=objtiletype[tiletypeindex].n;
                    objtiles[path[i]].e=objtiletype[tiletypeindex].e;
                    objtiles[path[i]].s=objtiletype[tiletypeindex].s;
                    objtiles[path[i]].w=objtiletype[tiletypeindex].w;

                    document.getElementById("tile"+path[i]).style.backgroundColor="#FF4400";
                }

                sendmessage("Click <span style='cursor:pointer' onclick='reset();timer_start();'><b>here</b></span> to begin");
            }


Now add the global variable gametime.

            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=[];
            var path=[];
            var gametime;
            var started=false;


Next, create your timer_start() function. This basically puts an initial value of 60 seconds in the pnlTimer div, clears the gametime variable, then sets a timer function to the decrement_timer() function. And of course, sets the global variable started to true.

            function sendmessage(varmessage)
            {
                document.getElementById("pnlMessage").innerHTML=varmessage;
            }

            function timer_start()
            {
                var timer=document.getElementById("pnlTimer");
                timer.innerHTML="Time left: 60 seconds";

                clearInterval(gametime);

                gametime=setInterval(function(){timer_decrement();},1000);
                started=true;
            }


You got it, another function! Create the timer_decrement() function.

            function timer_start()
            {
                var timer=document.getElementById("pnlTimer");
                timer.innerHTML="Time left: 60 seconds";

                clearInterval(gametime);
                gametime=setInterval(function(){timer_decrement();},1000);
                started=true;
            }

            function timer_decrement()
            {

            }


Now run the code. Click on "begin". Does the timer display say "60 seconds"? Now click on the tiles. Do they rotate?

My timer isn't moving!

That's because we haven't written any code for the timer_decrement() function. Here goes...
            function timer_decrement()
            {
                var timer=document.getElementById("pnlTimer");
                var seconds=timer.innerHTML;
                seconds=seconds.replace(" seconds","");
                seconds=seconds.replace("Time left: ","");
                seconds=parseInt(seconds);

                if (seconds==0)

                {
                    clearInterval(gametime);
                    alert("Times up! Better luck next time!");
                    started=false;
                }
                else
                {
                    seconds--;
                    timer.innerHTML="Time left: "+seconds+" seconds";
                }
            }


This basically gets the value of the pnlTimer div. If the time is now 0, it resets gametime, sets the started variable to false, and prompts the player. If not, it decrements the value and puts it back in the div.

Refresh. Is your timer moving now?

How do I win the game?

Well, as you probably know by now, rotating the tiles until there is an unbroken line from the start tile to the end tile, wins the game. But how does the script know? The answer is in the rotatetile() function. Add this segment. The function findcurrentpath() checks the board, after every rotation. If it finds an unbroken path from start tile to the end tile, it returns true. And that's when you can prompt the player with a congratulatory message, clear the gametime variable, set the started variable back to false, and put a "replay" link in the pnlMessage div.
            function rotatetile(vartile,varsec)
            {
                if (started)
                {
                    var timer=document.getElementById("pnlTimer");
                    var seconds=timer.innerHTML;
                    var degrees=vartile.style.transform;
                    degrees=degrees.replace("rotate(","");
                    degrees=degrees.replace("deg)","");
                    degrees=parseInt(degrees)+90;
                    vartile.style.transition="all "+varsec+"s";
                    vartile.style.transform="rotate("+degrees+"deg)";
                    vartile.style.webkitTransition="all "+varsec+"s";
                    vartile.style.webkitTransform="rotate("+degrees+"deg)";    

                    var id=parseInt(vartile.id.replace("tile",""));
                    var n,e,s,w;
                    n=objtiles[id].w;
                    e=objtiles[id].n;
                    s=objtiles[id].e;
                    w=objtiles[id].s;

                    objtiles[id].n=n;
                    objtiles[id].e=e;
                    objtiles[id].s=s;
                    objtiles[id].w=w;

                    if (findcurrentpath())
                    {
                        clearInterval(gametime);
                        alert("Congratulations! You have found true love!");
                        sendmessage("Well done! Click <span style='cursor:pointer' onclick='reset();timer_start();'><b>here</b></span> to replay");
                        started=false;   
                    }           
                }
                else
                {
                    alert("Please start the game first.");
                    return false;
                }
            }


Phew! Got all that? Now it's time for one last function, which is findcurrentpath(). First, we declare variables next_id and current_id. current_id is set to the first element in the path array, which is the id of the start tile. At the end of it, if next_id is the same as the last element in the path array, the function returns true. Otherwise, it returns false.

            function findcurrentpath()
            {
                var next_id,current_id=path[0];   

                if (next_id==path[path.length-1])

                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

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


Now declare the variables trynext and direction. trynext is exactly what the name suggests - a temporary variable for feeling out the next tile's id. direction is meant to hold the next possible direction.

            function findcurrentpath()
            {
                var next_id,current_id=path[0];
                var trynext;
                var direction;

                if (next_id==path[path.length-1])
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }


direction will never be the direction that the next tile came from. For example, if the next possible direction is east, then for the tile eastwards of the current tile, the next possible direction is never west.









n possible










(current)
w not possible


(next)


e possible










s possible






This is reflected in the next series of If statements. Since the start tile has only one exit point, it's easy to determine the one possible direction.

            function findcurrentpath()
            {
                var next_id,current_id=path[0];
                var trynext;
                var direction;

                if (objtiles[current_id].n==1) direction="n";

                if (objtiles[current_id].s==1) direction="s";
                if (objtiles[current_id].e==1) direction="e";
                if (objtiles[current_id].w==1) direction="w";   

                if (next_id==path[path.length-1])
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }


Another Do-while loop. next_id is set at a default of "-1". If a next tile is available, this value is changed  to the id of the next tile. This continues until no next tile is available (ie, next_id remains at a value of "-1") or the value of next_id is the same as the last element of the path array.

            function findcurrentpath()
            {
                var next_id,current_id=path[0];
                var trynext;
                var direction;

                if (objtiles[current_id].n==1) direction="n";
                if (objtiles[current_id].s==1) direction="s";
                if (objtiles[current_id].e==1) direction="e";
                if (objtiles[current_id].w==1) direction="w";   
           
                do
                {
                    next_id=-1;
                }
                while (next_id!=-1&&next_id!=path[path.length-1])

                if (next_id==path[path.length-1])
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }


Here, let's create the If statement for "n", ie. direction north. First, we check if it's at the top row. If so, north is a dead end and no further action is necessary. next_id remains at "-1" and findcurrentpath() returns false. If not, we check for the s property of the tile above the current tile. If it's not "1", that means there's no connection. Again, no further action is necessary. next_id remains at "-1" and findcurrentpath() returns false. If there is a connection, trynext is set to the id of the tile above the current tile. We then check for the exit points of that tile - north, east or west. We won't check south because the previous tile was from the south!

            function findcurrentpath()
            {
                var next_id,current_id=path[0];
                var trynext;
                var direction;

                if (objtiles[current_id].n==1) direction="n";
                if (objtiles[current_id].s==1) direction="s";
                if (objtiles[current_id].e==1) direction="e";
                if (objtiles[current_id].w==1) direction="w";   
           
                do
                {
                    next_id=-1;

                    if (direction=="n")
                    {
                        if (objtiles[current_id].y>0)
                        {
                            trynext=current_id-6;

                            if (objtiles[trynext].s==1)

                            {
                                next_id=trynext;
                                current_id=next_id;
                                if (objtiles[trynext].n==1) direction="n";
                                if (objtiles[trynext].e==1) direction="e";
                                if (objtiles[trynext].w==1) direction="w";
                            }
                        }
                    }

                }
                while (next_id!=-1&&next_id!=path[path.length-1])

                if (next_id==path[path.length-1])
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }


So do the same for the other directions.
            function findcurrentpath()
            {
                var next_id,current_id=path[0];
                var trynext;
                var direction;

                if (objtiles[current_id].n==1) direction="n";
                if (objtiles[current_id].s==1) direction="s";
                if (objtiles[current_id].e==1) direction="e";
                if (objtiles[current_id].w==1) direction="w";   
           
                do
                {
                    next_id=-1;

                    if (direction=="n")
                    {
                        if (objtiles[current_id].y>0)
                        {
                            trynext=current_id-6;

                            if (objtiles[trynext].s==1)
                            {
                                next_id=trynext;
                                current_id=next_id;
                                if (objtiles[trynext].n==1) direction="n";
                                if (objtiles[trynext].e==1) direction="e";
                                if (objtiles[trynext].w==1) direction="w";
                            }
                        }
                    }

                    if (direction=="s")
                    {
                        if (objtiles[current_id].y<3)
                        {
                            trynext=current_id+6;

                            if (objtiles[trynext].n==1)
                            {
                                next_id=trynext;
                                current_id=next_id;
                                if (objtiles[trynext].s==1) direction="s";
                                if (objtiles[trynext].e==1) direction="e";
                                if (objtiles[trynext].w==1) direction="w";   
                            }
                        }
                    }

                    if (direction=="e")
                    {
                        if (objtiles[current_id].x<5)
                        {
                            trynext=current_id+1;

                            if (objtiles[trynext].w==1)
                            {
                                next_id=trynext;
                                current_id=next_id;
                                if (objtiles[trynext].n==1) direction="n";
                                if (objtiles[trynext].s==1) direction="s";
                                if (objtiles[trynext].e==1) direction="e";   
                            }
                        }
                    }

                    if (direction=="w")
                    {
                        if (objtiles[current_id].x>0)
                        {
                            trynext=current_id-1;
                           
                            if (objtiles[trynext].e==1)
                            {
                                next_id=trynext;
                                current_id=next_id;
                                if (objtiles[trynext].n==1) direction="n";
                                if (objtiles[trynext].s==1) direction="s";
                                if (objtiles[trynext].w==1) direction="w";   
                            }
                        }
                    }
                }
                while (next_id!=-1&&next_id!=path[path.length-1])

                if (next_id==path[path.length-1])
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

That's it! Time to test your game!

Run your code. I've left the path shaded in orange to help see if what you have is correct. Click on "begin" and start rotating your tiles. When there's an unbroken line between the start tile and the end tile, you should see the victory prompt.

Now remove this line or comment it out.

            function reset()
            {
                var start_id,end_id;
                var tiletypeindex;

                do
                {
                    resetboard();
                    start_id=generaterandomno(0,3) * 6;
                    objtiles[start_id].n=objtiletype[8].n;
                    objtiles[start_id].s=objtiletype[8].s;
                    objtiles[start_id].e=objtiletype[8].e;
                    objtiles[start_id].w=objtiletype[8].w;
                    end_id=(generaterandomno(1,4) * 6) - 1;
                    objtiles[end_id].n=objtiletype[9].n;
                    objtiles[end_id].s=objtiletype[9].s;
                    objtiles[end_id].e=objtiletype[9].e;
                    objtiles[end_id].w=objtiletype[9].w;

                    document.getElementById("tile"+start_id).style.backgroundPosition=objtiletype[8].pos;
                    document.getElementById("tile"+end_id).style.backgroundPosition=objtiletype[9].pos;

                    createpath(start_id,end_id);
                }
                while (path[path.length-1]==-1);

                for (var i=1;i<path.length-1;i++)
                {
                    tiletypeindex=generaterandomno(0,3);

                    if (objtiles[path[i-1]].x==objtiles[path[i]].x&&objtiles[path[i+1]].x==objtiles[path[i]].x)
                    {
                        tiletypeindex=generaterandomno(4,7);
                    }

                    if (objtiles[path[i-1]].y==objtiles[path[i]].y&&objtiles[path[i+1]].y==objtiles[path[i]].y)
                    {
                        tiletypeindex=generaterandomno(4,7);
                    }

                    document.getElementById("tile"+path[i]).style.backgroundPosition=objtiletype[tiletypeindex].pos;

                    objtiles[path[i]].n=objtiletype[tiletypeindex].n;
                    objtiles[path[i]].e=objtiletype[tiletypeindex].e;
                    objtiles[path[i]].s=objtiletype[tiletypeindex].s;
                    objtiles[path[i]].w=objtiletype[tiletypeindex].w;

                    //document.getElementById("tile"+path[i]).style.backgroundColor=="#FF4400";
                }

                sendmessage("Click <span style='cursor:pointer' onclick='reset();timer_start();'><b>here</b></span> to

begin");
            }


And you have a game!

Next

We'll put the finishing touches on this game. Up the difficulty level a bit. It will be short, I promise.


No comments:

Post a Comment