SitePoint Sponsor

User Tag List

Results 1 to 9 of 9
  1. #1
    SitePoint Addict
    Join Date
    Oct 2005
    Posts
    288
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)

    Image objects in an array

    I'll be darned -- I cannot get the concept.

    Here's the code. When I use context.drawImage directly in the imageObject.onload function, it draws the card on the canvas. But when I capture the drawImage into an array and later context.drawImage using the array I get nothing.

    What concept am I missing?

    Code:
    <!DOCTYPE html>
    <html>
     <head>
      <title>Simple Solitare</title>
      <meta name="Description" content="Solitare that compares adjacent / adjacent - 3 card for omatch on suit or ordinal.">
      <style>
       #canvas {
    
       }
      </style>
     </head>
     <body>
      <canvas id='canvas' width='600' height='300'>
        Canvas not supported
      </canvas>
      <script src="deck.js"></script>
      <script type="text/javascript">
       var canvas = document.getElementById('canvas'),
          context = canvas.getContext('2d');
       var cardsPlayed = new Array;
    	 Deal(); 				// populate a card deck (in deck.js)
    	 card = GetNextCard();	// (also in deck.js)
       var imageObj = new Image();
         imageObj.src = card + ".gif";
         imageObj.onload = function() {
    //	 	  context.drawImage(imageObj, 69, 10);  	WORKS WHEN UNCOMMENTED
    	 	cardsPlayed[0] = imageObj;
         } // end onload.function
    // alert("game2c");
    context.drawImage(cardsPlayed[0], 69, 10);		//	DOES NOT WORK
      </script>
     </body>
    </html>
    Regards,

    grNadpa

  2. #2
    Community Advisor bronze trophy
    fretburner's Avatar
    Join Date
    Apr 2013
    Location
    Brazil
    Posts
    1,446
    Mentioned
    45 Post(s)
    Tagged
    13 Thread(s)
    Hi grNadpa,

    Quote Originally Posted by Grnadpa View Post
    What concept am I missing?
    This is related to the problem you were having in the previous thread with the loop. When JavaScript does I/O (such as loading an image), it does so in a non-blocking fashion. This means that the rest of the program continues executing in the meantime, and an event is fired when loading is complete to allow you to take further action. This is why your code doesn't execute linearly.

    Code JavaScript:
    imageObj.onload = function() {
            cardsPlayed[0] = imageObj; // executes 2nd
    }
     
    context.drawImage(cardsPlayed[0], 69, 10); // executes 1st, array is still empty

  3. #3
    SitePoint Addict
    Join Date
    Oct 2005
    Posts
    288
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by fretburner View Post
    Hi grNadpa,

    This is related to the problem you were having in the previous thread with the loop. ... This is why your code doesn't execute linearly.
    Lest you think I ignored your comments in that thread, I did go through the reference on closures you provided. Obviously, I did not understand it -- at least in the context of these timing issues.

    .And, of course, I trust you recognize the code you provided to me.

    Though I sort of understand why javascript would, for resource utilization optimization, choose to allow code to execute while waiting for another action to complete. But I thought the onload implemented function was supposed to delay the execution of the body of that anonymous function until the load completed..

    Obviousy not ... and I once again resemble the south end of a north-going horse.

    So, here's what I really want to do.

    I want my GetNextCard (snippet below) to return a card object consisting of the card.ordinal (a value from 1 (ace) to 13 (king), the card.suit (enum "s", "h", "d" and"c") and card.face which is the image rendered for the suit-ordinal combination . (i.e. for Ace of Spades the ordinal is "1", the suit is "s" and the face is pulled from 1s.gif)

    I would then place that object into an array, so that every time I add to that array, I can clear the canvas and rebuild a new one by iterating through that array.
    Code:
    	function GetNextCard() {
    		if(cardsLeft < 1) return false;
    		// get a number between 1 and the number of remaining cards
    		i = Math.floor(Math.random() * cardsLeft) + 1;
    		var nextCard = deck[i];			// pull that card
    		deck[i] = deck[cardsLeft];      // replace that card with the last in the deck
    		--cardsLeft;
    //	here is where I would like to build my card object: nextCard [ordinal, suit, face]
    		return nextCard;
    	} // end function GetNextCard
    But before I do that, I was simply trying to pull the image without displaying it on the canvas.

    Is there hope?

  4. #4
    Community Advisor bronze trophy
    fretburner's Avatar
    Join Date
    Apr 2013
    Location
    Brazil
    Posts
    1,446
    Mentioned
    45 Post(s)
    Tagged
    13 Thread(s)
    Apologies if anything which follows appears to be patronising or labouring the point, I'm just trying to ensure my explanations are as clear and unambiguous as possible.

    Quote Originally Posted by Grnadpa View Post
    Though I sort of understand why javascript would, for resource utilization optimization, choose to allow code to execute while waiting for another action to complete. But I thought the onload implemented function was supposed to delay the execution of the body of that anonymous function until the load completed..
    You're absolutely right.. any function you assign to imageObj.onload will be executed at some later time (once the load has completed) and the rest of your script will continue to execute in the meantime. That's why when you call context.drawImage(cardsPlayed[0], 69, 10) from outside of the onload function it executes immediately (before the image has loaded and been assigned to the array), and so cardsPlayed is still an empty array at this point.

    Quote Originally Posted by Grnadpa View Post
    So, here's what I really want to do.

    I want my GetNextCard (snippet below) to return a card object consisting of the card.ordinal (a value from 1 (ace) to 13 (king), the card.suit (enum "s", "h", "d" and"c") and card.face which is the image rendered for the suit-ordinal combination . (i.e. for Ace of Spades the ordinal is "1", the suit is "s" and the face is pulled from 1s.gif)

    I would then place that object into an array, so that every time I add to that array, I can clear the canvas and rebuild a new one by iterating through that array.
    One way to tackle this would be to hand off responsibility for loading an image and adding it to the canvas to a separate function (which is essentially what we ended up with in the previous thread).

    The code below is very similar to what you wanted to achieve, but rather than have each card object manage its own image, that is handled by the displayCard function so it can add the image to the canvas as soon as it's loaded. We also end up with an array, cardsPlayed, which contains the card objects dealt from the deck.

    Code JavaScript:
    var canvas = document.getElementById('canvas'),
       context = canvas.getContext('2d');
     
    var cardsPlayed = new Array;
    Deal();
    card = GetNextCard();
     
    displayCard(card, 69, 10);
    cardsPlayed.push(card);
     
    function displayCard(card, xPos, yPos) {
        var img = new Image();
        img.src = card.ordinal + card.suit + ".gif";
        img.onload = function() {
            context.drawImage(img, xPos, yPos);
        }
    }

    For this to work, I've made a small change to your Deal function, so that it creates an array of simple objects:
    Code JavaScript:
    function Deal(){
        for(i=1;i<14;i++){
            deck[i] = { ordinal: i, suit: "c" };
            deck[i+13] = { ordinal: i, suit: "d" };
            deck[i+26] = { ordinal: i, suit: "h" };
            deck[i+39] = { ordinal: i, suit: "s" };
        }
        cardsLeft = 52;
    }

    Is this closer to what you wanted to achieve?

  5. #5
    SitePoint Addict
    Join Date
    Oct 2005
    Posts
    288
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by fretburner View Post
    Apologies if anything which follows appears to be patronising or labouring the point, I'm just trying to ensure my explanations are as clear and unambiguous as possible.
    Your responses are invaluable. I'm the one who should make amends for overindulging your willingness and patience.

    I may have some follow-up after studying your response. Hopefully there will be an epiphany.

  6. #6
    SitePoint Addict
    Join Date
    Oct 2005
    Posts
    288
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    No epiphany.

    Getting a blank screen after the "if(debug){alert(img.src)};" in function DisplayCard(card, xPos, yPos).
    Code:
    <!DOCTYPE html>
    <html>
     <head>
      <title>Simple Solitare</title>
      <meta name=description" content="Solitare that compares adjacent / adjacent - 3 card for omatch on suit or ordinal.">
      <meta name="author" content="Brian Case with major assist from SitePoint's fretburner">
     </head>
     <body>
      <canvas id='canvas' width='600' height='300'>
        Canvas not supported
      </canvas>
    
      <script type="text/javascript">
    // ----- Globals ----- 
    
       var canvas = document.getElementById('canvas'),
          context = canvas.getContext('2d');
    //	  canvas.addEventListener("mousedown", GetSourceIndex, false);
    //  	  canvas.addEventListener("mouseup", OverlayCard, false);
    	debug = true;  
      	deck=new Array(53);     // populated in function Deal()
    	cardsLeft = 52;			// number of cards not yet played
    	cardsPlayed = new Array(53); // cards on canvas reference
    	countPlayed = 0;		// logical length to cardsPlayed Array
    //	sourceIndex;            // index to cardsPlayed array on mousedown
    //	targetIndex;            // index to cardsPlayed array on mouseup
    	xOffset = 69;           // card width plus spacing (image is 53px)
    	yOffset = 84;           // card height plus spacing (image is 68 px)
    // ---- Functions ----- 
    	function Deal(){		// build a card deck
    		if(debug){alert("Deal");}
    		for(i=1;i<14;i++){	// iterate from ace (1) to king (13)
    			deck[i] = { ordinal:i, suit: "c" };		// clubs
    			deck[i+13] = { ordinal:i, suit: "d" };  // diamonds
    			deck[i+26] = { ordinal:i, suit: "h" };  // hearts
    			deck[i+39] = { ordinal:i, suit: "s" };  // spades
    		} // end for
    		cardsLeft = 52;		// used in simulated shuffle GetNextCard();
    	} // end function Deal 	
    
    	function GetNextCard() {            // simulate a shuffled card
    		if(debug){alert("GetNextCard");}
    		if(cardsLeft < 1) return false; // all cards played
    		// get a number between 1 and the number of remaining cards
    		i = Math.floor(Math.random() * cardsLeft) + 1;
    		var nextCard = deck[i];			// pull that card (to return)
    		deck[i] = deck[cardsLeft];      // replace that card with the last in the deck
    		--cardsLeft;                    // logical length of the remaining deck
    		return nextCard;                // return the card pulled
    	} // end function GetNextCard
        function DisplayCard(card, xPos, yPos){             // show card on canvas
    		if(debug){alert("DisplayCard(" + card.ordinal + card.suit + "," + xPos + "," + yPos + ")");}
    		var img = new Image();
    		img.src = card.ordinal + card.suit + ".gif";    // build file name
    		if(debug){alert(img.src)};
    		img.onload = function() {					    // wait for image to load
              context.drawImage(img, xPos, yPos);           // place it on canvas
    		} // end onload
    	} // end function DisplayCard
    	
    	function RefreshCanvas(cardsPlayed, countPlayed) {	// Present cards to user
    	   	if(debug){alert("RefreshCanvas(" + cardsPlayed.length + "," + countPlayed + ")");}
    	// clearing canvas based on 
    	// http://stackoverflow.com/questions/2142535/how-to-clear-the-canvas-for-redrawing
    //		context.save();   // Store the current transformation matrix
    		// Use the identity matrix while clearing the canvas
    //		context.setTransform(1, 0, 0, 1, 0, 0);
    //		context.clearRect(0, 0, 600, 300);
    		// Restore the transform
    //		context.restore();
    	// end of copy from stackoverflow
    
    		faceDown  = { ordinal:0, suit: "facedown" };
    		if(debug) {alert("faceDown: " + faceDown.ordinal + faceDown.suit);}
          	if(debug) {alert("DisplayCard(faceDown, " + xOffset + " ," + yOffset + ")");}
    		DisplayCard(faceDown,xOffset,yOffset);             // simulates deck image
    //		DisplayCard(cardsPlayed[0], xOffset*2, yOffset);   // draw cardDealt
    //		y = yOffset*2;									   // new row
    //		for (i=1; i<= countPlayed; i++) {
    //			DisplayCard(playedCards[i], xOffset*i, y);	   // line up cards in row
    //		} // end for
    	} // end function RefreshCanvas
    
    // ----- initialization -----
    	if(debug){alert("Initialize");}
    	Deal();											// Create a deck of cards
    	cardsPlayed[0] = GetNextCard();					// Draw the first card
    	countPlayed = 1;								// Count the card as played
    	if(debug){
    		aCard = cardsPlayed[0];
    		alert("cardsPlayed[0]: " + aCard.ordinal + aCard.suit);
    	}
    	RefreshCanvas(cardsPlayed, countPlayed);								// Start the game
      </script>
     </body>
    </html>
    BTW, I chose not to use the "cardsPlayed.push(card);" as I am not sure I could reference a middle row in the table if I'm only allowed a "pull". Not likely, I know. But obviously I haven't had much luck with my assumptions about javascript.

  7. #7
    Community Advisor bronze trophy
    fretburner's Avatar
    Join Date
    Apr 2013
    Location
    Brazil
    Posts
    1,446
    Mentioned
    45 Post(s)
    Tagged
    13 Thread(s)
    I've copied and pasted your code pretty much as is (I just changed the path to the card images to match the location on my laptop) and it seems to work fine. I'm getting all the alert dialogs, and a single card, face down, displayed on the canvas.

    By the way, as a more powerful alternative to using the alert() function for debugging, you might want to try the console.log() function. This will output everything to your browser's JS console (open with ctrl+shift+J in Chrome, ctrl+shift+k in Firefox, and F12 in Internet Explorer), which is easier than having to OK a load of alert pop-ups. As well as logging strings it also allows you to log objects and arrays to the console so you can inspect their contents.

  8. #8
    SitePoint Addict
    Join Date
    Oct 2005
    Posts
    288
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    This is apparently a firefox issue -- at least as it is installed on my Windows8 system. I'm running Firefox version 24. As you experienced, it works in Chrome and Explorer. So I don't feel quite so stupid.

    The console.log() function certainly seems the better option.

    Finally, what may clear up my mental fog -- what kind of situation would I want code to execute while my image is loading? As a mainframer who started out with wiring boards for unit record equipment, punched card readers and tape drives I understand buffering. And in multi-user environments I can understand multiprocessing strategies in the operating systems. But what kind of situation would benefit from the parallel process at the application level within a single client?
    Last edited by Grnadpa; Oct 25, 2013 at 19:15. Reason: spelling eror

  9. #9
    Community Advisor bronze trophy
    fretburner's Avatar
    Join Date
    Apr 2013
    Location
    Brazil
    Posts
    1,446
    Mentioned
    45 Post(s)
    Tagged
    13 Thread(s)
    Quote Originally Posted by Grnadpa View Post
    This is apparently a firefox issue -- at least as it is installed on my Windows8 system. I'm running Firefox version 24. As you experienced, it works in Chrome and Explorer. So I don't feel quite so stupid.
    Interestingly, if you swap out all the alert calls for console.log it seems to work in Firefox as well.. I'm not really sure why that makes the difference, but it does.

    Quote Originally Posted by Grnadpa View Post
    Finally, what may clear up my mental fog -- what kind of situation would I want code to execute while my image is loading? As a mainframer who started out with wiring boards for unit record equipment, punched card readers and tape drives I understand buffering. And in multi-user environments I can understand multiprocessing strategies in the operating systems. But what kind of situation would benefit from the parallel process at the application level within a single client?
    Well, with JS the most common scenario which benefits from this is making network requests to a remote server. If the script waited for each procedure to complete before continuing with the program, a slow connection would cause a noticable delay to the end-user.

    In some modern JS applications, when the user deletes a record the request is dispatched to the server. The program makes the assumption that the request will be processed OK, and so removes the item from the user's screen before it receives a reply from the server (which could come some seconds after). This makes the application feel quick and responsive.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •