Use the JavaScript canvas to implement puzzle games

Source: Internet
Author: User
Tags base64 pow
This article brings the content is about the use of JavaScript canvas to realize the puzzle games, there is a certain reference value, the need for friends can refer to, I hope to help you.

If you want to complete an interesting project using a variety of technologies such as canvas, native drag, and local storage in JavaScript, this article will work well for you

1 Introduction and source code

The puzzle games in the project use JavaScript original, compared to similar features on the site, it uses more advanced technology, more powerful, and also contains more advanced ideas in program development, from which you will be able to learn:

    • FileReader, Image object with canvas to compress the picture, cutting skills.

    • Learn the most commonly used collision detection, state monitoring, refresh and hold state processing methods in the development of small game.

    • Learn more about the details of drag-and-drop swapping elements, learn about dynamic element binding events, and how callback functions are handled.

Project Source-github

Here is an example diagram of the game interface:

2 Realization Ideas

According to the game interface diagram we can complete such a small game into the following steps to achieve:

    • 1. Drag the image to the specified area, use the FileReader object to read the Base64 content of the picture, and add it to the image object

    • 2. When the image object is loaded, use canvas to scale the picture, then take the base64 content of the thumbnail, add it to another thumbnail image object, and save the thumbnail base64 to local storage (localstorage)

    • 3. When the thumbnail image object is loaded, once again using canvas to cut the thumbnail, the game will cut the thumbnail into 3*4 total of 12 equal parts, using local storage to save each cut thumbnail base64 content, the thumbnail order is broken, using the IMG tag displayed on the Web page

    • 4. When the thumbnail slices are added to the Web interface, adding a registration drag event for each thumbnail slice, so that the thumbnail slices can be exchanged with each other, in this process, add to the thumbnail tile sequence status monitoring, once the puzzle completed, directly display the complete thumbnail, complete the game

From the above analysis of the production process of small games, the 4th step is the implementation of the program function of the key and difficult, in each of the above steps have a lot of small details to pay attention to and discussion, the following I will be detailed analysis of each step of the implementation of the details, said the bad place, welcome to the message.

3 Detailed details of development

3.1 Picture content Read and load

In the 1th step of the game development, we drag the picture to the designated area, how does the program get the picture content information? How does the FileReader object convert the picture information into Base64 string content? When the image object gets the base64 content of the image, how does it initialize the load? With these questions, let's look at the key code that implements the first step in the implementation of the project.

var droptarget = document.getElementById ("DropTarget"), output = document.getElementById ("Ul1"), Thu             mbimg = document.getElementById ("thumbimg"); The relevant code is omitted here ... function handleevent (event) {var info = "", reader = new F                Ilereader (), files, I, Len;                Eventutil.preventdefault (event);                Localstorage.clear ();                    if (Event.type = = "Drop") {files = Event.dataTransfer.files;                    len = files.length;                    if (!/image/.test (Files[0].type)) {alert (' Please upload file of image type ');                    } if (Len > 1) {alert (' The number of uploaded images cannot be greater than 1 ');                    } var canvas = document.createelement (' canvas ');                    var context = Canvas.getcontext (' 2d '); var img = new Image (),//original ThumbiMG = new Image ();                    Equal to scaled thumbnail reader.readasdataurl (files[0]);                    Reader.onload = function (e) {img.src = E.target.result; }//The Picture object is loaded, and the image is scaled.                        The maximum width after scaling is 300 pixels img.onload = function () {var targetwidth, targetheight; Targetwidth = this.width > 300?                        300:this.width;                        Targetheight = Targetwidth/this.width * this.height;                        Canvas.width = Targetwidth;                        Canvas.height = Targetheight;                        Context.clearrect (0, 0, targetwidth, targetheight);                        Context.drawimage (IMG, 0, 0, targetwidth, targetheight);                        var tmpsrc = Canvas.todataurl ("Image/jpeg");                        Store the full thumbnail source Localstorage.setitem (' Fullimage ', tmpsrc) locally;       THUMBIMG.SRC = TMPSRC;             }//Omit relevant code here ...         Eventutil.addhandler (DropTarget, "DragEnter", handleevent);         Eventutil.addhandler (DropTarget, "DragOver", handleevent);            Eventutil.addhandler (DropTarget, "drop", handleevent); }

The idea of this code is to get the droptarget of the target object in the drag area first, and drag and drop the listener event for DropTarget. The code used in the Eventutil is a simple object that I encapsulated in the common functions of adding events, event objects, and so on, the following is a simple and simple code to add registration events, there are many other packages, the reader can self-check, the function is relatively simple.

var eventutil = {    addhandler:function (element, type, handler) {        if (element.addeventlistener) {            Element.addeventlistener (type, handler, false);        } else if (element.attachevent) {            element.attachevent ("on" + type, handler), or        else {            element["on" + type] = Han Dler        ,}    },        //omitted here ...}

When the user drags the picture file to the zone target object DropTarget, the DropTarget event object obtains the file information through Event.dataTransfer.files, filtering the file (only the image content is limited and only one picture is allowed). After getting the contents of the file, read the contents of the file using FileReader object Reader, use its Readasdataurl method to read the Base64 content of the picture, assign to the SRC attribute of the Image object IMG, you can wait until the IMG object is initialized to load. Let the canvas do the next processing for IMG. Here is a key point to note: Be sure to wait until the IMG loading is complete, then use the canvas for the next step, or there may be a picture corruption situation. The reason is that when the SRC attribute of IMG reads the base64 content of the picture file, it may not have loaded the contents into memory, and the canvas begins to process the picture (the picture is incomplete at this point). So we can see that the canvas of the image processing is placed in the Img.onload method, there will be a case behind the program, and then no longer repeat.

3.2 Picture equal than zoom and local storage

In the first step we finished reading the contents of the dragged file and successfully loaded it into the Image object img. Next we use the canvas to compare the image to the scale, the picture is scaled, we adopt the strategy is to limit the maximum width of the picture is 300 pixels, let's take a look at this part of the code:

 Img.onload = function () {var targetwidth, targetheight; Targetwidth = this.width > 300?                        300:this.width;                        Targetheight = Targetwidth/this.width * this.height;                        Canvas.width = Targetwidth;                        Canvas.height = Targetheight;                        Context.clearrect (0, 0, targetwidth, targetheight);                        Context.drawimage (IMG, 0, 0, targetwidth, targetheight);                        var tmpsrc = Canvas.todataurl ("Image/jpeg");                        Store the full thumbnail source Localstorage.setitem (' Fullimage ', tmpsrc) locally;                    THUMBIMG.SRC = TMPSRC; }

After determining the scaled width targetwidth and height targetheight, we use the canvas's DrawImage method to compress the image, before which we'd better use the canvas's clearrect to clean the canvas first. After scaling the picture, use the Todataurl method of the canvas, get the Base64 content of the zoom graph, assign the SRC attribute to the new Zoom Graph image Object Thumbimg, and then the zoom graph is loaded and the next cutting process is done. The base64 content of the zoom graph is stored using localstorage, with the key named "Fullimage". The local storage of the browser localstorage is hard storage, the content will not be lost after the browser refreshes, so that we can maintain the state of the data during the game, this is explained in detail later, we need to know that localstorage is a size limit, the maximum is 5 m. This is why we first compress the image, reduce the size of the stored data, and save the base64 content of the zoom diagram. For information about what to store during development, the next section will have a detailed description of the legend.

3.3 Thumbnail Cutting

The work done after the thumbnail is generated is to cut the thumbnail, and the same is the DrawImage method that uses the canvas, and the corresponding processing must be done after the thumbnail is loaded (that is, thumbimg.onload), as we have said before. Let's take a look at the source code in detail:

Thumbimg.onload = function () {//width/height of each slice [cut into 3*4 format] var slicewidth, Slicehe                            ight, sliceBase64, n = 0, outputelement = ", Slicewidth = THIS.WIDTH/3,                        Sliceheight = THIS.HEIGHT/4, sliceelements = [];                        Canvas.width = Slicewidth;                        Canvas.height = Sliceheight;                                for (var j = 0; J < 4; J + +) {for (var i = 0; i < 3; i++) {                                Context.clearrect (0, 0, slicewidth, sliceheight);                                Context.drawimage (thumbimg, Slicewidth * I, Sliceheight * j, Slicewidth, Sliceheight, 0, 0, slicewidth, sliceHeight);                                SliceBase64 = Canvas.todataurl ("Image/jpeg");                                Localstorage.setitem (' slice ' + N, sliceBase64); To prevent a picture three pixel problem from occurring, add Display:blo to the picture propertiesCK newelement = "<li name=\" "+ N +" \ "style=\" margin:3px;\ "></li> "; Scramble the picture order based on random numbers (Math.random () > 0.5)?                                Sliceelements.push (newelement): Sliceelements.unshift (newelement);                            n++; }}//splicing element for (var k = 0, Len = sliceelements.length ; K < Len;                        k++) {outputelement + = Sliceelements[k];                        } localstorage.setitem (' ImageWidth ', This.width + 18);                        Localstorage.setitem (' ImageHeight ', This.height + 18);                        Output.style.width = this.width + + ' px ';                        Output.style.height = this.height + + ' px '; (output.innerhtml = outputelement) &&                        Begingamesinit ();                    Droptarget.remove (); }

The above code for everyone is not difficult to understand, that is, the thumbnail is divided into 12 slices, here I will explain to you a few easy to confuse the place:

    • 1. Why do we cut the image again, the code as follows, starting from the column loop?

for (var j = 0; J < 4; J + +) {for    (var i = 0; i < 3; i++) {        //Omit logical code here    }  }

This question everyone thinks carefully to understand, when we cut the picture, we should record the original sequence of each picture slice. In the program we use N to represent the original order of the picture slices, and this n is recorded in the name attribute of each element of the picture slice. In the subsequent game process we can use the element's getattribute (' name ') method to remove the value of N, to determine whether the picture slices are dragged to the correct position, so as to determine whether the game is over, and now talk about this problem may be a bit confusing, we will discuss in detail later, I give a picture to help you understand the image slice position ordinal information N:

The ordinal n is zero-based in order to align with the sub-element coordinates selected by getElementsByTagName () in JavaScript.

    • 2 The goal of our 3rd step is not only to cut the thumbnail into small slices, but also to shuffle the slices of the image, and how does this in the Code program be implemented?
      Read the Code program we know that each tile we generate constructs an element node: newElement = "<li name=\"" + n + "\" style=\"margin:3px;\"></li>"; . We declare an array of new nodes on the outside sliceelements, we will put it in the sliceelements array each generation of a new element node, but we add the new node to the sliceelements head or tail is random, The code is this:

(Math.random () > 0.5)? Sliceelements.push (newelement): Sliceelements.unshift (newelement);

We know that math.random () generates a number between [0, 1], so the order of the Web nodes generated by these slices is disrupted after the canvas cuts the thumbnails into slices. Re-assemble the nodes after the order is scrambled:

splicing element for (var k = 0, len = sliceelements.length; k < Len; k++) {    outputelement + = Sliceelements[k];}

Then add the node to the Web page, and it will naturally appear that the picture slices are disrupted.

    • 3. The DOM nodes that we generate based on thumbnail slices are dynamically added elements, how do we bind events to such dynamic elements? The events in our project that are bound to each thumbnail slice DOM node are "drag-and-swap" and have relationships with other nodes, and we want to make sure that all the nodes are loaded and then bound to the event, how do we do that?

The following line of code, though simple, is very ingenious:

(output.innerhtml = outputelement) && begingamesinit ();

The students with development experience know && and | | is a short-circuit operator, meaning in the code: only if the slice element nodes are added to the
Web pages before they are initialized to these node binding events.

3.4 Local Information Store

Local storage is used many times in the code, so let's explain in detail what information needs to be stored in the game development process. Here is an example of the information I need to store (obtained from the browser console):

The browser local storage localstorage is stored in key:value form, and we see what we have stored in this time:

    • Fullimage: Image thumbnail base64 encoded.

    • ImageWidth: Drag the width of the area picture.

    • ImageHeight: Drag the height of the area image.

    • slice*: The base64 content of each thumbnail slice.

    • Nodepos: The position coordinate information of the current thumbnail is saved.

The information to save the fullimage thumbnail is when the source thumbnail is displayed at the end of the game, and the picture is presented according to the content in Fullimage. Imagewidth,imageheight,slice*,nodepos is designed to prevent the storage of data loss caused by browser refreshes, and when the page is refreshed, the browser loads the unfinished game content based on locally stored data. Where Nodepos is stored locally when dragging on a thumbnail slice, and it changes as the tile position changes, which is how it tracks the state of the game, we'll talk about it again in the next code feature display.

3.5 drag-and-drop event registration and monitoring

The next thing we want to do is the most important part of the game, or first to analyze the code, first of all, before the event registration initialization work:

The game starts initializing function Begingamesinit () {    aLi = output.getelementsbytagname ("li");    for (var i = 0; i < ali.length; i++) {        var t = ali[i].offsettop;        var L = ali[i].offsetleft;        Ali[i].style.top = t + "px";        Ali[i].style.left = l + "px";        Apos[i] = {left:l, top:t};        Ali[i].index = i;        Record the location information down        nodepos.push (ali[i].getattribute (' name '));    }    for (var i = 0; i < ali.length; i++) {        ali[i].style.position = "absolute";        Ali[i].style.margin = 0;        Setdrag (Ali[i]);}    }

You can see that this part of initializing the binding event code is: Record the position of each picture slice object coordinates related information is recorded into the object properties, and for each object is registered drag events, the collection of objects by the Ali Array Unified management. It is worth mentioning that the location of the image slice index is the location of the slice, and the picture slice we mentioned earlier the Name property holds the information n is where the picture slice should be located, and before the game is finished, they are not necessarily equal. To all the picture slices the value of the Name property and its properties are equal to the end of the game (because the user has finished stitching the image correctly), the following code is used to determine whether the game state is finished, it looks more intuitive:

Determines whether the game ends function Gameisend () {    for (var i = 0, len = ali.length; i < Len; i++) {        if (Ali[i].getattribute (' Nam E ')! = Ali[i].index) {            return false;        }    }    Subsequent processing code omitted ...}

Let's say a few words about drag-and-drop exchange code-related logic, drag and drop the Exchange code as shown:

Drag function Setdrag (obj) {obj.onmouseover = function () {obj.style.cursor = "move";    Console.log (Obj.index); } Obj.onmousedown = function (event) {var scrolltop = Document.documentElement.scrollTop | | document.body.scrol        Ltop; var scrollleft = Document.documentElement.scrollLeft | |        Document.body.scrollLeft;        Obj.style.zIndex = minzindex++;        Calculates the distance between the mouse and the dragged object when the mouse is pressed DISX = Event.clientx + scrollleft-obj.offsetleft;        Disy = Event.clienty + scrolltop-obj.offsettop;            Document.onmousemove = function (event) {//] calculates the position of P when the mouse is dragged var L = event.clientx-disx + scrollleft;            var t = event.clienty-disy + scrolltop;            Obj.style.left = l + "px";            Obj.style.top = t + "px";            for (var i = 0; i < ali.length; i++) {ali[i].classname = "";            } var oNear = findmin (obj);     if (oNear) {onear.classname = "active";       }} document.onmouseup = function () {document.onmousemove = null;         Move event Document.onmouseup = null when mouse bounces off;            Move out up event, empty memory//detect if it is encountered, in swap position var oNear = findmin (obj);                if (oNear) {onear.classname = "";                ONear.style.zIndex = minzindex++;                Obj.style.zIndex = minzindex++;                Startmove (ONear, Apos[obj.index]);                Startmove (obj, Apos[onear.index], function () {gameisend ();                });                Exchange Index var t = Onear.index;                Onear.index = Obj.index;                Obj.index = t;                Exchange location information in this store var tmp = Nodepos[onear.index];                Nodepos[onear.index] = Nodepos[obj.index];                Nodepos[obj.index] = tmp;            Localstorage.setitem (' Nodepos ', nodepos);   } else {startmove (obj, Apos[obj.index]);         }} clearinterval (Obj.timer); Return false;//The lower version of the Prohibition symbol}}

The function of this code is as follows: Drag a picture slice, and when it overlaps with other picture slices, it will swap the position with the closest image to the top left corner and exchange its location index to update the NODEPOS in the local storage information. After the move is completed, the game is judged to be over, and if not, the next user's drag-and-drop exchange is expected.
Let me explain some of the more difficult points in this piece of code:

    • 1. How is the picture slice in the process of being dragged to determine if it collided with other image slices? This is a typical collision detection problem.
      The code for implementing collision detection in a program is this:

Collision detection Function Coltest (obj1, obj2) {    var t1 = obj1.offsettop;    var r1 = obj1.offsetwidth + obj1.offsetleft;    var B1 = obj1.offsetheight + obj1.offsettop;    var L1 = obj1.offsetleft;    var t2 = obj2.offsettop;    var r2 = obj2.offsetwidth + obj2.offsetleft;    var b2 = obj2.offsetheight + obj2.offsettop;    var L2 = Obj2.offsetleft;    ' If (T1 > b2 | | R1 < L2 | | B1 < t2 | | L1 > R2) ' {        return false;}    else {        return true;}    }

This code seems to be very little information, in fact, it is also very good to understand, to determine whether two slices of the picture is a collision, as long as they do not have a collision in the case of exclusion. This is a bit similar to the logic of the non-yes no, two slices and indeed there are only two cases: collision, non-collision. This code in the figure is a case of non-collision: if (t1 > b2 || r1 < l2 || b1 < t2 || l1 > r2) returns FALSE, else returns TRUE.

2. When the collision detection is complete, how do you find the nearest element in the top left corner of the picture slice?

This is what the code looks like:

The Pythagorean theorem asks for distance (the distance from the upper left corner) function Getdis (obj1, obj2) {    var a = Obj1.offsetleft-obj2.offsetleft;    var b = obj1.offsettop-obj2.offsettop;    Return Math.sqrt (Math.pow (A, 2) + Math.pow (b, 2));} Find the closest function findmin (obj) {    var mindis = 999999999;    var minindex =-1;    for (var i = 0; i < ali.length; i++) {        if (obj = = Ali[i]) continue;        if (Coltest (obj, ali[i])) {            var dis = Getdis (obj, ali[i]);            if (Dis < Mindis) {                mindis = dis;                Minindex = i;}}    }    if (Minindex = =-1) {        return null;    } else {        return ali[minindex];}    }

Because they are rectangular chunks, the distance from the upper-left corner is calculated using the Pythagorean theorem, which I believe we all understand. The principle of finding the closest element is also simple, which is to traverse all the elements that have already been collided, and then compare the minimum values computed by the Pythagorean theorem to return the elements. The code also uses a more general method, first declaring a very large value of the minimum value, when there is a collision element than its hour, and then a smaller value of the minimum value, after the completion of the traversal, the element that returns the minimum value can be.

    • 3. After each exchange of the picture block, how to monitor and determine whether the game has ended?

The answer is the callback function, the image slice Exchange function through the callback function to determine whether the game is over, the game is the end of the judgment function before we have said. The image slicing Exchange function is to add gameisend as a callback function, so that after each picture slice move Exchange is completed, the game will be judged whether it is over. Picture slices of the exchange function is more complex, interested students can study, the following is its implementation code, we focus on the understanding of the addition of the callback function to monitor whether the game is over.

Gets the element function GetClass (CLS) {var ret = [] through class;    var els = document.getelementsbytagname ("*"); for (var i = 0; i < els.length; i++) {//judgment Els[i] whether there is a CLS in this classname;.        IndexOf ("CLS") determines the subscript in the presence of the CLS, if subscript >=0 is present; if (els[i].classname = = = CLS | | els[i].classname.indexof ("CLS") >=0 | | els[i].classname.indexof ("CLS") >=0 | | els[i        ].classname.indexof ("CLS") >0) {Ret.push (els[i]); }} return ret;} function GetStyle (obj,attr) {//Solve JS compatibility problem get the correct property value return Obj.currentstyle?obj.currentstyle[attr]:getcomputedstyle ( Obj,false) [attr];} function Gameend () {alert (' Game over! ');}    function Startmove (obj,json,fun) {clearinterval (Obj.timer);        Obj.timer = setinterval (function () {var isstop = true;            for (Var attr in json) {var icur = 0;            Determine if the motion is not a transparency value if (attr== "opacity") {icur = parseint (parsefloat (GetStyle)) obj,attr);     }else{icur = parseint (GetStyle (obj,attr));       } var ispeed = (json[attr]-icur)/8; Movement speed if greater than 0 is rounded down, if less than 0 want to take the whole; ispeed = ispeed>0?            Math.ceil (ispeed): Math.floor (Ispeed);            Determine if all movements are complete if (Icur!=json[attr]) {isstop = false; }//Motion start if (attr== "opacity") {Obj.style.filter = "alpha: (Opacity:" + (Json[attr]+ispeed                )+")";            Obj.style.opacity = (json[attr]+ispeed)/100;            }else{obj.style[attr] = icur+ispeed+ "px";            }}//Determine if all complete if (isstop) {clearinterval (Obj.timer);            if (fun) {fun (); }}},30);}

4 Additions and summaries

4.1 Features in the game that deserve to be perfected

I think there are two places in the game that deserve optimization:

    • 1. Add thumbnails to the puzzle games, as thumbnails help provide ideas for users who play the game. We've also saved the Base64 content of thumbnails in the browser's local storage, so it's easy to implement them.

    • 2. The cache sometimes also makes people very painful, for example, in the game some users want to start again, and our little game only after the game is finished to empty the cache, refresh the page, the game can start again. This is a bad experience for the user, we can add a Reset game button, empty the cache and optimize some logic after the game is over.

These features are interesting for small partners to try.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.