Previous Flipping cards - simple animation with images. Next

The previous lesson described how to make a solitaire concentration game using the canvas.

When the player selected a card, we used the canvas `itemconfigure` command to change the image of a card back to the image of a card face.

It would look cooler to show the card flipping over, instead of just changing it from front to back.

When we create an image with the `image create` command, we've created an object, just like we did with the `snack::sound` command. The image objects have commands associated with them, just like the sound objects do.

One of the image object's commands is `copy`. We can use this command to copy one image to another. The nifty thing is that when we copy one image into another, we can also modify it with the option/value pairs.

Here's a couple of the things we can do. We can control where the new image gets copied into our original image, and we can control the size of the image we copy. We can even control the X and Y size separately.

We control the size by sub-sampling the image. Sub-sampling means that instead of taking every pixel from the new image and moving it into our original image, we skip a pixel, or two pixels, or 3 pixels, etc.

If we skip every other pixel and every other row, the new image will be 1/2 the size of the original. If we skip 2 pixels and 2 rows (and only take every 3'd pixel from the new image), we'll make an image that's 1/3 the size of the original.

When we subsample an image, we can tell Tcl/Tk to subsample the columns and rows at a different rate. We do this by giving the `-subsample` two arguments. The first argument will be the sub-sampling rate for the X dimension (the columns) and the second will be the sub-sampling rate for the Y dimension (the rows).

A value of 1 means take every pixel. A value of 2 means to skip every other pixel. A value of 3 means to skip 2 pixels, etc.

Here's an example that loads one of the card images and makes two sub-sampled copies of the image. We create a rectangle for each image, and then create the images over the rectangle to show what part of the image was copied.

 `````` # Create a card image image create photo card -file "s_q.gif" set ht [image height card] set wd [image width card] image create photo card2 -height \$ht -width \$wd card2 copy -subsample 2 1 card image create photo card3 -height \$ht -width \$wd card3 copy -subsample 3 1 card # Create and grid a canvas to work with canvas .c -background black grid .c .c create rectangle 0 0 \$wd \$ht -fill gray .c create image 0 0 -anchor nw -image card .c create rectangle 100 0 [expr 100 + \$wd] \$ht -fill gray .c create image 100 0 -anchor nw -image card2 .c create rectangle 200 0 [expr 200 + \$wd] \$ht -fill gray .c create image 200 0 -anchor nw -image card3 ``````

This code makes images like this:

The left-most image is the original, the middle one is sub-sampled every other pixel in the X dimension. the rightmost image is sub-sampled every third pixel in the X dimension.

Try typing that code into Komodo Edit and running it. Then try changing the the sub-sampling options to see how it changes the image.

The image copy command will start copying at the upper left hand corner of the destination image. We can change this using the `-to` option. We can use both the `-subsample` and `-to` options in the same command.

Here's a summary of the options we've just discussed.

Option Arguments Description
`-to` left top right bottom Copy the image into these coordinates
`-subsample` xCount yCount Skip xCount pixels across and yCount pixels down when copying. This reduces the size of an image.

Lets try using just one image (`card2`) and copy the `card` image to it with different sub-sampling. The code looks like this:

 `````` # Create a card image image create photo card -file "s_q.gif" set ht [image height card] set wd [image width card] image create photo card2 -height \$ht -width \$wd card2 copy -subsample 2 1 card card2 copy -subsample 3 1 card card2 copy -subsample 4 1 card # Create and grid a canvas to work with canvas .c -background black grid .c .c create rectangle 0 0 \$wd \$ht -fill gray .c create image 0 0 -anchor nw -image card .c create rectangle 100 0 [expr 100 + \$wd] \$ht -fill gray .c create image 100 0 -anchor nw -image card2 ``````

You'll end up with a result like this:

Yuck. What happened is that Tcl/Tk copied an image sub-sampled to every other pixel, then copied one sub-sampled every third pixel over that, and then copied one sub-sampled every fourth pixel over that.

But it only changed the pixels it was copying. It didn't change the other pixels.

Sometimes, this is what we want to do, but it makes a funky looking card when we're trying to write code that looks like a card being flipped over.

We probably need to learn a new command.

The image object also has a `put` command. We can tell Tcl/Tk what color (or colors) to put into an area of an image.

For instance, to fill an image with a new color (like gray), we can use this command:

 `````` card2 put gray -to 0 0 \$ht \$wd ``````

Try adding this before the last copy in the example above to see if it solves the problem (it does).

Here's a summary of the new commands:
Command Description
`copy` Copy a new image into the original image
`put` Put a new color at a location (or locations) in the image. You can use the `-to` option with this command.

With the `copy` and `put` image commands and the `-subsample` and `-to` options, we can make images that look like they're being flipped around their center.

But we don't want to show a bunch of images side by side. That's great to see what's going on, but it doesn't look like a card flipping over.

We could do a loop like this:

 `````` delete any previous image on canvas create image object copy card with appropriate subsampling create new image on canvas ``````

This would actually work, but it's kind of kludgey, and it takes a lot of computer resources to create and destroy things. It's much faster to just change them a little.

We can use the `itemconfigure` command that we looked at in the previous lesson to change the image the canvas is displaying to a temporary image. Then we can copy the card image into the temporary image with different sub-samplings and different locations.

Now, we're ready to flip the cards. Here's the procedure that will do the trick. Notice that we use `after` and `update idle` in the loops.

We use the `after` command to slow the computer down a little bit. Without the `after` commands making it pause between images, the cards would flip so fast we wouldn't seem them changing.

The `update idle` command makes the computer update the display immediately.

In this procedure we make a new image object that we can copy the original images into. We change the sub-sampling each time we go through a loop, and calculate a new location around the center of the card to copy the image into.

This makes it look as if we were turning the card over until it's edge on to us.

Once we've made the first image very narrow, we start another loop where we use the second image and subsample less each time. This makes it look like we're continuing to flip the card over and showing more and more of the second image each time.

This procedure is another generic type procedure. We can change the `start` and `end` images to flip a card from face down to face up, or from face up to face down.

We could even use images of Harry Potter and Goyle to make it look like Harry was flipping and transforming into Goyle!

 `````` ################################################################ # proc flipImageX {canvas canvasID start end background}-- # Makes it appear that an image object on a canvas is being flipped # Arguments # canvas The canvas holding the image # canvasID The identifier for this canvas item # start The initial image being displayed # end The final image to display # background The color to show behind the image being flipped. # This is probably the canvas background color # # Results # configuration for the canvas item is modified. # proc flipImageX {canvas canvasID start end background} { global concentration # Get the height/width of the image we'll be using set height [image height \$start] set width [image width \$start] # The image will rotate around the X axis # Calculate and save the center, since we'll be using it a lot set centerX [expr \$width / 2] # Create a new temp image that we'll be modifying. image create photo temp -height \$height -width \$width # Copy the initial image into our temp image, and configure the # canvas to show our temp image, instead of the original image # in this location. temp copy \$start \$canvas itemconfigure \$canvasID -image temp update idle after 25 # copy the start image into the temp with greater # subsampling (making it appear like more and more of an # edge view of the image). # Move the start of the image to the center on each pass # through the loop for {set i 2} {\$i < 8} {incr i} { set left [expr \$centerX - \$width / (2 * \$i)] set right [expr \$centerX + \$width / (2 * \$i)] temp put \$background -to 0 0 \$width \$height temp copy -to \$left 0 \$right \$height -subsample \$i 1 \$start update idle after 25 } # copy the end image into the temp with less and less # subsampling (making it appear like less and less of an # edge view of the image). # Move the start of the image away from thecenter on each pass # through the loop for {set i 8} {\$i > 1} {incr i -1} { set left [expr \$centerX - \$width / (2 * \$i)] set right [expr \$centerX + \$width / (2 * \$i)] temp put \$background -to 0 0 \$width \$height temp copy -to \$left 0 \$right \$height -subsample \$i 1 \$end update idle after 25 } # configure the canvas to show the final image, and # delete our temporary image \$canvas itemconfigure \$canvasID -image \$end image delete temp } ``````

We call the `flipImageX` procedure from the `playerTurn` procedure instead of using the `itemconfigure` command that we used in the previous lesson.

The new `playerTurn` procedure looks like this. Notice that after a player finds a match, we configure the image to be blank and use the `bind` command to put a binding on the images with an empty command. Assigning an empty command means to do nothing. That's the same as destroying the binding.

 `````` ################################################################ # proc playerTurn {position}-- # Selects a card for comparison, or checks the current # card against a previous selection. # Arguments # position The position of this card in the deck. # # Results # The selection fields of the global array "concentration" # are modified. # The GUI is modified. # proc playerTurn {position} { global concentration set card [lindex \$concentration(cards) \$position] flipImageX .game card_\$position back \$card gray set rank [lindex [split \$card _] 1] # If concentration(selected,rank) is empty, this is the first # part of a turn. Mark this card as selected and we're done. if {{} eq \$concentration(selected,rank)} { # Increment the turn counter incr concentration(turn) set concentration(selected,rank) \$rank set concentration(selected,position) \$position set concentration(selected,card) \$card } else { # If we're here, then this is the second part of a turn. # Compare the rank of this card to the previously saved rank. if {\$position == \$concentration(selected,position)} { return } # Update the screen *NOW* (to show the card), and pause for one second. update idle after 1000 # If the ranks are identical, handle the match condition if {\$rank eq \$concentration(selected,rank)} { # Increase the score by one incr concentration(player,score) # Remove the two cards and their backs from the board .game itemconfigure card_\$position -image blank .game itemconfigure card_\$concentration(selected,position) -image blank .game bind card_\$position "" .game bind card_\$concentration(selected,position) "" # Check to see if we've won yet. if {[checkForFinished]} { endGame } } else { # If we're here, the cards were not a match. # flip the cards to back up (turn the cards face down) flipImageX .game card_\$position \$card back gray flipImageX .game card_\$concentration(selected,position) \ \$concentration(selected,card) back gray } # Whether or not we had a match, reset the concentration(selected,rank) # to an empty string so that the next click will be a select. set concentration(selected,rank) {} } } ``````

Open the previous game in Komodo Edit and change these two procedures.

Try changing the number of milliseconds that the `after` command pauses for to see how it changes the speed of the cards flipping.

Write a `flipImageY` procedure and use that to flip the cards end-over-end instead of side to side. Change the `itemconfigure` command in `playerTurn` to use the `flipImageY` to a blank image instead.

This lesson looked at doing some simple animations. The important parts are:

• You can copy one image to another with the image `copy` command.

• You can make an image smaller by sub-sampling (taking every other or every third, etc pixel) by using the `copy` with the `-subsample` option.

• You can subsample at different rates in the X and Y direction.

• You can control where an image is copied by using the ` copy` command and the `-to` option.

• You can make an animation by changing images,

• You need to use the `after` command to slow the computer down enough for people to see the different images in an animation.

• You need to use the `update idle` to force the computer to update the screen after you change images in an animation.

You might have noticed that index we're using to keep track of the score is `player,score`. I wonder if that means that we could also have a `computer,score` index and change this one-person game into a two-person game to play against the computer...

Find out the answer to this and other thrilling questions in the next lesson...