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?


<!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

Hi grNadpa,

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.

imageObj.onload = function() {
        cardsPlayed[0] = imageObj; // executes 2nd
}

context.drawImage(cardsPlayed[0], 69, 10); // executes 1st, array is still empty

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.

	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?

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.

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.

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.

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:

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?

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.

No epiphany.

Getting a blank screen after the “if(debug){alert(img.src)};” in function DisplayCard(card, xPos, yPos).

<!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.

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.

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?

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.

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.