Second Summer 2002


Lab Notes Fourteen: IceBlox
Here's a review on loading (and manipulating) images:

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;


public class One extends Applet implements MouseListener,
                                           MouseMotionListener {

  Image[] frames = new Image[10];

  int currentFrame = 0;

  int locX = 60, locY = 60;

  public void init() {

    Component component = this;
    MediaTracker tracker = new MediaTracker(component);

    for (int i = 0; i < 10; i++) {
      String imgName = "images/T" + (i + 1) + ".gif";
      frames[i] = this.getImage(this.getCodeBase(), imgName);
      tracker.addImage(frames[i], 0);
    }

    System.out.println("Images are being loaded...");

    try {
      tracker.waitForAll();
    } catch (Exception e) { 
      System.out.println("Something went wrong..."); 
    }

    if (tracker.isErrorAny()) {

       System.out.println("*** isErrorAny() reports an error... ");

    }  else if (tracker.checkAll()) {

       System.out.println("All's well.");

    }

    MouseListener mouseListener = this;
    this.addMouseListener(mouseListener);

    MouseMotionListener mouseMotionListener = this;
    this.addMouseMotionListener(mouseMotionListener);

  }

  public void paint(Graphics g) {

    ImageObserver imgObserver = this;

    g.drawImage(frames[currentFrame], locX, locY, imgObserver);

    // g.drawString("(" + locX + ", " + locY + ")", locX, locY);

  }

  public void mouseMoved(MouseEvent e) {

     currentFrame = (currentFrame + 1) % 10;

     locX = e.getX();
     locY = e.getY();
     repaint();
  }
  public void mouseDragged(MouseEvent e) { }
  public void mouseEntered(MouseEvent e) { }
  public void mouseExited(MouseEvent e) { }
  public void mousePressed(MouseEvent e) { }
  public void mouseReleased(MouseEvent e) { }
  public void mouseClicked(MouseEvent e) { }


} 

Class Project: Object-Oriented Programming vs. Procedural Programming. IceBlox, A Case Study.

Take a look at this picture. It contains 48 smaller images, plus a larger one, for the title.

Eight images (the flames, the scores, the single coin) are transparent: the background of this page is white, the picture appears inside a table whose background color is lightgrey, that can be seen.

The small images are squares of 30 pixels, the title is 224 pixels wide (and 60 pixels tall).

We start the appletviewer on the following iceblox.html file:

<html>
  <head><title>IceBlox - by Karl Hornell (Apr. 8, 1996)</title></head>
  <body bgcolor=white>

    <applet code="Iceblox.class" width=400 height=400>

    </applet>

  </body>
</html>
The following introductory screen greets us:

After seven seconds (if we don't take any action) a second screen shows:

After eight more seconds (if we still don't do anything) the third introductory screen is shown:

After seven more seconds (if we still don't do anything) we get back to the first screen.

When we press the space bar we start the game, and here's the screen we start from:

The snapshot here is taken not immediately, but shortly after the game started, so the flames (that normally enter through the lower right corner of the field) had time to wander a bit.

Let's see if we can program that much.

NOTE: Point your appletviewer to the following URL to play the game:

http://www.cs.indiana.edu/classes/a348/
                                  CTED/
                            moduleFour/
                              lectures/iceblox/reference/iceblox.html
This way you can get a broader, complete perspective, and now let's get started.

import java.awt.*; 

public final class Iceblox extends java.applet.Applet implements Runnable {
    public void init() {
	System.out.println("Initializing applet..."); 
    }
    public void start() {
	System.out.println("Starting applet..."); 
    }
    public void stop() {
	System.out.println("Stopping applet..."); 
    }
    public void paint(Graphics g) {
	System.out.println("Painting applet..."); 
    }
    public void run() {
	System.out.println("Running thread..."); 
    }
}
The starting point simply provides methods that recognize an applet's milestones (transitions, states). If you run this applet you can get an output similar to the one presented below:
frilled.cs.indiana.edu%appletviewer iceblox.html
Initializing applet...
Starting applet...
Painting applet...
Painting applet...
Painting applet...
Stopping applet...
Starting applet...
Painting applet...
Stopping applet...
frilled.cs.indiana.edu%
The three methods init(), start(), and stop() together with paint(java.awt.Graphics) are responsible for the noticed output, and we have discussed their machinery a bit earlier. The last of the five methods, run() is there because we plan on doing something else in addition to being an applet, with this program. This only marks an intent, as does implements Runnable (at the same time).

We assume that the double buffering concepts are still fresh in our minds.

http://www.cs.indiana.edu/classes/a201-dger/sum2002/notes/TwentySeven.html
And we start:
import java.awt.*; 

public final class Iceblox extends java.applet.Applet implements Runnable {
    public void init() {
	System.out.println("Initializing the applet..."); 
    }
    Thread game; 
    public void start() {
	System.out.println("Starting the applet..."); 
	if (game == null) {
	    game = new Thread(this); 
	    game.start(); 
	}
    }
    public void stop() {
	System.out.println("Stopping the applet..."); 
	game = null; 
    }
    public void paint(Graphics g) {
	System.out.println("Painting the applet..."); 
    }
    int counter; 
    long snooze = 200; // a fifth of a second 
    public void run() {
	while (game != null) {
	    try {
		game.sleep(snooze); 
	    } catch (InterruptedException e) { } 
	    counter = (counter + 1) & 15; // arithmetic modulo 16
	    System.out.println(counter); 
	}
    }
}
Running this would set the machinery in motion.

frilled.cs.indiana.edu%javac Iceblox.java
frilled.cs.indiana.edu%appletviewer iceblox.html
Initializing the applet...
Starting the applet...
Painting the applet...
1
2
3
4
5
6
7
8
9
10
11
Stopping the applet...
12
Starting the applet...
Painting the applet...
13
14
15
0
1
2
3
4
5
6
Stopping the applet...
7
Starting the applet...
Painting the applet...
8
9
10
11
12
13
14
Painting the applet...
15
0
1
2
3
4
5
6
7
8
9
Stopping the applet...
10
frilled.cs.indiana.edu%
Notice that the counter stays current between the destruction and then creation of a new Thread in the variable named game. The counter is global to it or, rather, to its run() method. Note also the arithmetic used with counter and the fact that the update and display of the counter are done at the same time with the program fulfilling its other duties as an applet with event-driven milestones.

One can try removing the code that makes the thread snooze, but one sees then how difficult it is for the applet to respond to anything else (which it does, but with extreme delay). We now update the applet as follows:

import java.awt.*; 

public final class Iceblox extends java.applet.Applet implements Runnable {
    Image offImage; 
    final int mainX = 390, mainY = 348; 
    Graphics offGraphics; 
    public void init() {
	setBackground(Color.yellow); 
	offImage = createImage(mainX, mainY); 
	offGraphics = offImage.getGraphics(); 
	offGraphics.setColor(Color.blue); 
	offGraphics.fillRect(0, 0, mainX, mainY); 
	resize(mainX, mainY); 
    }
    Thread game; 
    public void start() {
	if (game == null) {
	    game = new Thread(this); 
	    game.start(); 
	}
    }
    public void stop() {
	game = null; 
    }
    public void paint(Graphics g) {
	g.drawImage(offImage, 0, 0, this); 
    }
    public void update(Graphics g) {
	paint(g); 
    }
    int counter; 
    long snooze = 100; 
    public void run() {
	while (game != null) {
	    try {
		game.sleep(snooze); 
	    } catch (InterruptedException e) { }
	    counter = (counter + 1) & 255; 
	    offGraphics.setColor(Color.blue); 
	    offGraphics.fillRect(0, 0, mainX, mainY); 
	    offGraphics.setColor(Color.white); 
	    offGraphics.drawString("The counter now is: " + counter, 40, 40); 
	    System.out.println("The counter now is: " + counter); 
	    repaint(); 
	}
    }
}
A few notes on the code above:
  1. The background of the applet is Color.yellow, but you can't see it because we draw a Color.blue filled rectangle over it, and then resize() the applet.
  2. Notice the arguments to resize(): mainX and mainY are 390 and 348 respectively, and are declared final. They represent the width and the height of the area where any and all drawing will occur in this game.
  3. Using a double buffering approach we prepare the drawing in an Image off-screen, then show it in paint(). To eliminate the flicker we redefine update() so the screen is not cleared prior to invocation of paint().
  4. That means that we need to clear the screen ourselves, otherwise, the drawn value of the updated counter soon becomes illegible. Clearing the screen is done with the help of offGraphics by calling fillRect() on the entire applet.
  5. setBackground() and createImage() are methods inherited from java.awt.Component. Checking the specific hierarchy for java.applet.Applet located at this URL one can also notice that an applet is an ImageObserver (why, and why does it matter?).

    http://java.sun.com/products/jdk/1.2/docs/api/java/applet/package-tree.html

So far we have not done much, let's implement the sequence of three screens now.

import java.awt.*; 

public final class Iceblox extends java.applet.Applet implements Runnable {
    Image offImage; 
    final int mainX = 390, mainY = 348; 
    Graphics offGraphics; 
    int gameState; 
    public void init() {
	setBackground(Color.black); 
	offImage = createImage(mainX, mainY); 
	offGraphics = offImage.getGraphics(); 
	offGraphics.setColor(Color.black); 
	offGraphics.fillRect(0, 0, mainX, mainY); 
	resize(mainX, mainY); 
        gameState = 7; 
    }
    Thread game; 
    public void start() {
	if (game == null) {
	    game = new Thread(this); 
	    game.start(); 
	}
    }
    public void stop() {
	game = null; 
    }
    public void paint(Graphics g) {
	g.drawImage(offImage, 0, 0, this); 
    }
    public void update(Graphics g) {
	paint(g); 
    }
    int counter; 
    long snooze = 100; 
    public void run() {
	while (game != null) {
	    try {
		game.sleep(snooze); 
	    } catch (InterruptedException e) { }
	    counter = (counter + 1) & 255; 
	    switch (gameState) {
		case 7: 
		    drawIntro1(); 
		    break; 
		case 8: 
		    waitIntro1(); 
		    break; 
		case 9: 
		    drawIntro2(); 
		    break; 
		case 10: 
		    waitIntro2(); 
		    break;   
    	        case 11: 
		    drawIntro3(); 
		    break; 
 	        case 12:
		    waitIntro3(); 
		    break; 
 	        default: 
		    System.out.println("Unexpected gameState: " + gameState); 
                    break; 
	    } 
	    System.out.println("(" + gameState + ", " + counter + ")"); 
	    repaint(); 
	}
    }

    public void drawIntro1() { 
	offGraphics.setColor(Color.black); 
	offGraphics.fillRect(0, 0, mainX, mainY); 
	offGraphics.setColor(Color.white); 
	offGraphics.drawString("ACTORS AND OBJECTS"     , 145,  97); 
	offGraphics.drawString("Pixel Pete, the penguin", 180, 130); 
	offGraphics.drawString("Evil flames"            , 180, 170); 
	offGraphics.drawString("Ice cube"               , 180, 210); 
	offGraphics.drawString("Solid rock"             , 180, 250); 
	offGraphics.drawString("Frozen gold coin"       , 180, 290); 
	offGraphics.drawString("Press SPACE to start"   , 138, 330); 
        counter = 0;
        gameState = 8; // switching to a new state 
    } 

    public void waitIntro1() { 
	if (counter > 70) // time passes... where does this happen? 
	    gameState = 9; 
    } 

    public void drawIntro2() { 
	offGraphics.setColor(Color.black); 
        offGraphics.fillRect(0, 75, mainX, 230); 
	offGraphics.setColor(Color.white); 
	offGraphics.drawString("HOW TO PLAY"                   , 165, 97); 
	offGraphics.drawString("Move up, down, left, and right", 180, 122); 
	offGraphics.drawString("with the K, M, A, and D keys"  , 180, 137); 
        offGraphics.drawString("Walk against ice cubes"        , 180, 162); 
	offGraphics.drawString("to move them out of the way"   , 180, 177); 
	offGraphics.drawString("Walk against blocked"          , 180, 202); 
	offGraphics.drawString("ice cubes to crack them"       , 180, 217); 
	offGraphics.drawString("Free the gold coins by"        , 180, 242); 
	offGraphics.drawString("crushing the ice around them"  , 180, 257); 
	offGraphics.drawString("And watch out"                 , 180, 282); 
	offGraphics.drawString("for the flames"                , 180, 297); 
	gameState = 10; // moving into a new state 
	counter = 0; // counter counts time (waitIntro2() waits by it) 
    } 

    public void waitIntro2() { 
	if (counter > 80) // if eight seconds have passed 
	    gameState = 11; // move into the third introductory screen 
    } 

    public void drawIntro3() { 
	offGraphics.setColor(Color.black); 
	offGraphics.fillRect(0, 75, mainX, 230); 
	offGraphics.setColor(Color.white); 
	offGraphics.drawString("SCORING"                       , 180,  97); 
	offGraphics.drawString("Breaking ice,"                 , 180, 122); 
	offGraphics.drawString("5 points"                      , 180, 137); 
	offGraphics.drawString("Putting out flame"             , 180, 162); 
	offGraphics.drawString("with ice, 50 points"           , 180, 177); 
	offGraphics.drawString("Freeing coin,"                 , 180, 202); 
	offGraphics.drawString("100 points"                    , 180, 217); 
	offGraphics.drawString("Taking all coins and advancing", 180, 242); 
	offGraphics.drawString("to next level, 1000 points"    , 180, 257); 
	gameState = 12; 
	counter = 0; 
    } 

    public void waitIntro3() { 
	if (counter > 70) // seven seconds... 
	    gameState = 7; // going back to the first introductory screen 
    } 

}
Notice there's no event-handling, and that the text layout design has already been done for us.

We now need to bring the images into the picture.

This process has two steps:

In the process it will also become clear how we do animation.

To get there, we will need to start in a simple way, again.

Let's write an applet that reports the following five keystrokes:

Here's the basic code:
import java.awt.event.*; 

public final class Iceblox extends java.applet.Applet implements KeyListener {

    public void init() { 
	System.out.println("Greetings. I am an Applet. "); 
	System.out.println("I will now add myself as a KeyListener..."); 
	addKeyListener(this); 
	System.out.println("...done. I am ready now."); 
    }

    public void keyPressed (KeyEvent e) { 
	System.out.println("Key pressed"); 
    }

    public void keyReleased (KeyEvent e) { 
	System.out.println("Key released"); 
    }

    public void keyTyped (KeyEvent e) { 
	System.out.println("Key typed"); 
    }

}
This really has nothing to do (yet) with our next step, but we'll soon be using this pattern.

IceBlox Notes: Here are some notes about the code below. Feel free to use them to check your own findings, or simply use them in your development. There are 23 methods in the Iceblox class, let me number them here (while I list them) then briefly characterize each one.

  1. init
  2. run
  3. start
  4. stop
  5. keyDown
  6. keyUp
  7. prepareField
  8. showField
  9. gameLoop
  10. happyPenguin
  11. clearField
  12. fixDeath
  13. gameOver
  14. drawIntro1
  15. waitIntro1
  16. drawIntro2
  17. waitIntro2
  18. drawIntro3
  19. waitIntro3
  20. removeActor
  21. updateScore
  22. paint
  23. update
Let's start with 20 (removeActor). If you look at it you might guess what an actor should consist of:
a) a position (x, y)
b) a velocity (dx, dy)
c) a certain look (per each frame)
d) a direction of motion
e) a type (what kind of creature it is)
Starting with e) we have the following values and meanings:
  creature[i] is 1 that means actor i is a penguin
                 2                         moving ice block
                 3                         moving frozen coin
                 4                         flame
                 5                         flashing 50
                 6                         dummy
                 7                         skeleton
Note that the notion of actor here is a bit specific: an actor is something that moves. Thus an ice block starts by not being an actor at all (in the beginning, the only actors in the game are the penguin and the flames) but it becomes an actor if the penguin hits it and it can slide in that direction. It also is an actor only during the slide, when it stops it no longer has the attributes of an actor, and it simply stays there becoming simply a part of the static landscape). ,p> As far as d) is concerned here's the coding scheme:

   motion[i] is 0 that means actor i is stationary
                1                       moving left (west)
                2                              right
                3                              up
                4                              down
As far as c) goes look[i] is an index in the array small[] the contents of which is obvious, from the artwork in the previous set of notes (e.g., the frames for the happy penguin are 0 and 39, penguin coming down has the look of indices 1-2-3-2 in one continuous sequence, up is 4-5-6-5, a moving ice block will be as in small[16], the look of a frozen coin is 24, a flame will dance by the following rhythm: 32-33-34-35-36-35-34-33-32 in a loop. That, in effect, is also the meaning of animP and animF, as defined at the beginning of the class.

All actors are shown in pictures of 30x30 pixels. Both position and velocity are going to be expressed in pixels. As a matter of fact if you look at the following data structures:

- levFlame (how many flames at this level)
- levRock (how many rocks at this level)
- levSpeed (speed of moving flames per level)
- levIce (number of ice blocks at this level)
You will notice they all have six components. The goal of the game is to collect all the coins, by breaking the ice around them, and once the penguin collects them all (at a certain level) the game advances to the next. Starting at 0 there is no level higher than 5 (effMax), and the current level (effLevel) needs to be adjusted accordingly. Looking at these structures one can see, for example, that for level 4 (index 3) the penguin can expect 2 flames, moving fas (5 pixels at a time), and a starting game context that asks for 8 rocks and 29 ice blocks.

Ice blocks can be broken by the penguin, in 8 steps (for 5 points). Rocks are not breakable. Ice blocks destroy flames (for 50 points), and flames destroy the penguin (which has 3 extra lives). Breaking the ice around a coin gives you 100 points.

So dx and dy are the number of pixels the actor moves at one time. The values are basically 6 for the penguin and 15 for a moving ice block. Note that the flames *always* move slower than the penguin.

Remember that the look for an actor (penguin, flame, ice block with or without the coin inside) is a picture of 30x30 pixels. (Let us ignore for a moment the small penguin that's 15x15 inside a 30x30 image, that shows the spare lives). An important aspect of the pair (x[i], y[i]) that represents the location of actor [i] is that it represents the lower right corner of the image (the point that has the largest coordinates). This is important, because when we draw the image we need to subtract 30 from both: top left corner is used to anchor an image with drawImage, as known.

Now all these things cannot be deduced from method 20) but they can be mentioned here, as a starting point. Let's now look at the data structures.

So let's talk about int playArea[], which is, in Karl's coding, the grid behind the game's world. The world is 2 dimensional, but Karl's using a regular array to represent it as we discussed two weeks ago.

His world is like a chessboard, 13 squares wide (blockX) and 11 tall (this value stored in blockY), so you'd expect playArea to be 13x11, only it's 15x13, that is (blockX+2)*(blockY+3), and one can explain why through a number of observations from the code. But before we look at them let's see what one can store in playArea:

  - first off, line i column j in the 2-dimensional world of the penguin
    is location index (i * width + j) in the undimensional array playArea
    where the width is, of course, blockX + 2

  - if you store a value of 0 in playArea it means empty cell
                            1                      a rock
                            2                      an ice block
                              3                    cracked ice block
                              4                    cracked^2 ice block
                              5                    cracked^3 ice block
                              6                    cracked^4 ice block
                              7                    cracked^5 ice block
                              8                    cracked^6 ice block
                              9                    cracked^7 ice block
                           10                      a frozen coin
                             11                    a cracked frozen coin
                             12                    a cracked^2 frozen coin
                             13                    a cracked^3 frozen coin
                             14                    a cracked^4 frozen coin
                             15                    a cracked^5 frozen coin
                             16                    a cracked^6 frozen coin
                             17                    a cracked^7 frozen coin
Here are some notes on the issue of cracked ice blocks, with and without a coin inside: 9 and 17 are the last stage before the cell becomes empty. An ice block can't be moved (by becoming an actor) unless it's not cracked and if it gets a crack it will simply occupy that cell until it 'melts' away, by subsequent cracking.

Now let's go through the procedures (methods) one by one.

(Notes are going to be precise, but very brief).

1. Notes for init()

The whole image is mainX (390) by mainY (348) pixels with the top part dedicated to score and spare lives information (14 pixels) above a 3-d rectangle (4 pixels in height) so the game actually happens in an area that is playX wide (still 390) but playY pixels tall, that is 348 - 12 - 2 - 4 = 330 pixels). Note that the image for spare lives are shifted up by 16 pixels, and overlap 15 pixels each, horizontally. The maximum number of actors is set to 20.

2. Notes for run() - no notes

3. Notes for start() - no notes

4. Notes for stop() - no notes

5. Notes for keyDown(java.awt.Event e, iny key)

The variable dir is only for the penguin. Its values follow the earlier mentioned conventions of motion[i]. For the key to mean something it has to be 'a' (for left), 'd' (for right), 'k' (for up), or 'm' (for down), and the game state should be 'game loop' (gameState == 2). If we're not in the game loop we could be during the introduction (7-8-9-10-11-12, in a loop, they're all > 6) ready to start with a 'space' (32).

6. Notes for keyUp(java.awt.Event e, iny key)

Keeping the key pressed keeps the penguin going in whatever direction the key is sending it to. Releasing the key makes the penguin stationary.

7. Notes for prepareField()

Put rock everywhere, then make the inner ring (1-blockX, 1-blockY) empty. Something like this:

    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    1 1 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Also place a rock in playArea[1][1] (note I use the 2-d notation, which you should interpret as playArea[1 * (blockX + 2) + 1] where blockX is 13. (The extra level of rocks in the south simplifies the code in gameLoop).

Now,

a) for the current level of the game randomly place that many ice blocks b) randomly place as many rocks as the current level requires c) randomply place 5 coins
Then clear the starting square (which was thus protected during the random placement of ice blocks, rocks, frozen coins). That's where the penguin will be positioned at the beginning of the game.

The delay variable (snooze) is always 1/10 of a second. We indicate we now want to show the field by moving into that gameState (1, for showField()), and place the penguin, stationary, in playArea[1][1]. Notice the x and y of the penguin (actor with index 0). They're (30, 30) because of the associated semantics (lower-right corner, as opposed to top-left).

The next available index for actors is 1 (stored in variable actors) which also becomes the effective lengths of the parallel arrays (whose actual sizes are, of course, 20). creature[0] is penguin (1), and there's a creatureCount (here marked ccount) which is set to 0, so we need to add it to the set of features that 'describe' an actor.

The number of coins 'thawed' by the penguin at a certain levelis stored in a variable with the same name (coins). Number of flames currently in the game is also stored in a variable with the same name (flames). Notice that the look of the penguin is set to index 0 (mouth open hands down, first frame in the artwork) only to be replaced with the third frame (basic penguin, look[0]=2). Penguin's speed is 6 pixels (on the horizontal or the vertical). All the creatureCount values are set to 0 (an initialization step that would be automatic if this were an instance variable in a class, e.g., Creature).

8. Notes for showField()

Notice the use of counter, involved in the dimensions of the rectangle that clips the graphics context and grows, to provide the shutter effect. We switch to game state 2 (gameLoop) after 3 seconds.

9. Notes for gameLoop()

The method starts by making sure we have enough flames (for the current level). New flames are placed randomly on the vertical but non-randomly on the horizontal: if the penguin is in the western hemisphere a new flame will appear from the east; otherwise it will appear from the west.

If we need to create a new flame its speed is given by levSpeed[effLevel]. The type of creature is 4 (flame, as discussed before,) and notice that we always create the flame with an x for a location in playArea where there is a rock (the eastern and western outer borders).

Map this location to a cell j in playArea:

a) if it's on the west side then playArea[j-1] is the cell on the east border one line above (and there's a rock there) and playArea[j+1] is the cell to the right, just inside the game area;

b) if playArea[j] is on the east side, then playArea[j-1] is the cell to the left, just inside the game area, and playArea[j+1] is the first cell on the (extra) western border, and there's a rock there from prepareField().

So regardless of what border you're on, playArea[j-1] and playArea[j+1] will contain at least one rock (value of 1). We need the flame to be able to go into the game area, so we will accept the position only if there's also a value of 0 (zero) on j-1 or j+1. At that moment we have one more actor, and one more flame.

If the position is not good, we just pass on the calculation, and try again next time playLoop is called. This will be immediately (most likely, unless one of the other flames gets the penguin just then) because the rest of the code simply updates all the actors, then exits, without changing gameState, so we stay in the same switch case in run().

Updating the actors starts by increasing their creature counter (ccount) and then, based on the direction they're going (motion[i]) update x[i] or y[i], accordingly (only one, for up, down, left, or right). Since x and y are likely to change we compute the line (y[i]/30) and the column (x[i]/30), and remember that x and y start at 30 as they are the coordinates of the lower-right corner. We then translate this into a position (j) in playArea, in the usual way.

After this update we perform specific tasks, depending on type of creature:

  1. penguin:

    If the penguin is exactly centered in a square then make it stay (it may still have a direction, dir). If it stays check to see what it has in front (one and two squares). What is the index in playArea for the location that is in front of you when you face north, south, east, or west? If you face east or west that will be an index of plus/minus 1. If you face north or south that will be an index of plus/minus 15 or, rather, plus/minus (blockX + 2). That's what's in sideIX, and the inFront2 variable is where we store the value found two squares in front of the current position of the penguin. Notice that we only need to check if two squares to the north might come outside of the playArea (index is negative) since to the south we have a lot of rocks, in the extra layer.

    If the penguin does not have anything in front of it it resumes walking (that's only if it was already moving, as indicated by dir. Notice that when the key is released dir becomes zero). Otherwise we need to examine what the penguin is up against.

    If it's a block of ice (2, 10) with or without a coin, and inFront2 is empty, then it becomes an actor and it starts sliding. Notice the look and the type of creature. When we instantiate the actor we need the coordinates for it (an actor is born on the spot) so we create them using the penguin (x[i] and y[i] and the coorDx[], coorDy[] arrays). The new actor will move 15 pixels per cycle, in the direction dir (same as the penguin,) and the cell where it was in playArea becomes empty (and will hold a value of 0, zero).

    Otherwise inFront2 is not empty so if inFront we have an ice block (2, 3, 4, ..., 9, 10, 11, 12, ..., 17) we basically crack ice, so we increment its value (++) and ask if it's all cracked (9) or released coin (17). In the cases where the ice is completely removed or when the coin has been finally released (coins++) we update the score, and clear the cell. In the other cases the extra crack is rendered, showing image small[inFront+15]; you should note here the + 15 takes into account that inFront has the old value (whereas playArea has been updated).

    If in motion show next frame (motion[i] has the direction).

    If any of the other actors is a flame and within 20 pixels on either direction (horizontally or vertically) then it becomes a dummy (6,) that does not move, and the penguin becomes a skeleton (7).

  2. moving ice block:

    It stops and disappears if it fits the square and it has an obstacle in front of it. When an actor moves it is responsible for painting, as we will see in paint (which is done with double-buffering). Otherwise the static background is always shown (playField image, with playGraphics as the graphical context).

  3. moving frozen coin:

    Same thing, exactly the same (except for the index in small[]).

  4. flame:

    Update the look (frame). If it stays choose a random direction and go. When fitting a square precisely try to track penguin: if within three pixels horizontally or vertically, go towards it. (Basically, it only detects the penguin on the same line or column, not very smart flame). If roadblock, stop.

    Check all the actors, if one is a moving ice block or frozen coin and within 30 pixels, then become a flashing 50 (special kind of creature or actor). Score is updated. k=actors breaks the loop.

  5. flashing 50:

    One of the two cases where ccount really matters. For 2 seconds we're flashing the score, then remove the actor.

  6. dummy:

    When a flame hits the penguin the flame disappears.

  7. skeleton:

    The other case when ccount is useful. We show the 8 frames in the first 0.8 seconds, then stay on the last frame until 3 seconds are finished, then we update the lives. If the game is lost we set gameState to 5.

    Otherwise we simply restart the game (at the same level).

    End of case.

The end of this method checks to see if we got all coins. In that case we we get an extra 1000, and change to gameState 3 (happyPenguin(), which will update the level and switch to gameState 4 (clearField) only when counter becomes 35. Until then, for 3.5 seconds, we update counter (in run), and paint knows to make the distinction by switching between the two frames for a happy penguin (wings up and down).

10. Notes for happyPenguin() - see the paragraph above.

11. Notes for clearField() - same as showField(), except erasing. When erasing is done we go to prepareField again (game state becomes 0 again).

12. Notes for fixDeath() - it's the beginning of the end (gameState = 6). This image (final score, penguin image, etc.) is kept there for 8 seconds. At the end we switch to gameState 7, where we start everything all over.

13. Notes for gameOver() - see paragraph above.

14-19. Explained in previous web notes.

20. We started with it.

21. Very basic stuff.

22, 23. Basic double-buffering with a few things covered above.

This should be enough, so now let's look at the code.

// http://www.cs.indiana.edu/classes/a348/CTED/moduleFour/
                                                 lectures/
                                                  iceblox/
                                                reference/Iceblox.java

// Iceblox
// By Karl Hörnell, April 8 1996

import java.awt.*;
import java.awt.image.*;
import java.net.*;

public final class Iceblox extends java.applet.Applet implements Runnable
{

    int i,j,k;

    final int 
	playX=390,
	playY=330,
	mainX=390,
	mainY=348,
	smalls=48,
	blockX=13,
	blockY=11;

    final int animP[]={7,8,9,8,10,11,12,11,4,5,6,5,1,2,3,2};
    final int animF[]={32,33,34,35,36,35,34,33};
    final int levFlame[]={2,3,4,2,3,4},levRock[]={5,6,7,8,9,10};
    final int levSpeed[]={3,3,3,5,5,5},levIce[]={35,33,31,29,27,25};

    final int effMax=5;

    int playArea[];

    int gameState,counter,dir,inFront,inFront2,level,coins;
    int effLevel,lives=3;

    int x[],
	y[],
	dx[],
	dy[],
	motion[],
	look[],
	creature[],
	ccount[],
	actors,
	flames;

    int sideIX[]={0,-1,1,-15,15},
	coorDx[]={0,-30,30,0,0},
	    coorDy[]={0,0,0,-30,30};

    Image collection,offImage,playField,small[],title;

    Graphics offGraphics,playGraphics,tempG;

    MediaTracker tracker;

    ImageFilter filter;

    ImageProducer collectionProducer;

    long snooze=100,score;

    Thread game;

    Math m;
    
    public void init()
    {
	setBackground(Color.black);
	offImage=createImage(mainX,mainY);
	offGraphics=offImage.getGraphics();
	offGraphics.setColor(Color.black);
	offGraphics.fillRect(0,0,mainX,mainY);
	playField=createImage(playX,playY);
	playGraphics=playField.getGraphics();
	playGraphics.setColor(Color.black);
	tracker=new MediaTracker(this);
	collection = getImage(getCodeBase(),"iceblox.gif");
	tracker.addImage(collection,0);
	try
	    {
		tracker.waitForID(0);
	    }
	catch(InterruptedException e) {}
	collectionProducer=collection.getSource();
	small=new Image[smalls];
	k=0;i=0;j=0;
	while (k<smalls)
	    {
		filter=new CropImageFilter(j*30,i*30,30,30);
		small[k]=createImage
		    (new FilteredImageSource
			(collectionProducer,filter));
		tracker.addImage(small[k],1);
		/* small[k]=createImage(30,30);
		   tempG=small[k].getGraphics();
		   tempG.drawImage(collection,-j*30,-i*30,this);*/
		
		k++;
		j++;
		if (j==8)
		    {
			j=0;
			i++;
		    }
	    }
	filter=new CropImageFilter(0,180,224,64);
	title=createImage
	    (new FilteredImageSource
		(collectionProducer,filter));
	tracker.addImage(title,1);
	
	playArea=new int[(blockX+2)*(blockY+3)];
	x=new int[20];
	y=new int[20];
	dx=new int[20];
	dy=new int[20];
	look=new int[20];
	motion=new int[20];
	creature=new int[20];
	ccount=new int[20];
	
	gameState=7;
	try
	    {
		tracker.waitForID(1);
	    }
	catch(InterruptedException e) {}
	
	resize(mainX,mainY);
    }
    
    public void run()
    {
	while (game !=null)
	    {
		try
		    {
			game.sleep(snooze);
		    } catch (InterruptedException e) {}
		counter=(counter+1)&255;
		switch (gameState)
		    {
		    case 0:
			prepareField();
			break;
		    case 1:
			showField();
			break;
		    case 2:
			gameLoop();
			break;
		    case 3:
			happyPenguin();
			break;
		    case 4:
			clearField();
			break;
		    case 5:
			fixDeath();
			break;
		    case 6:
			gameOver();
			break;
		    case 7:
			drawIntro1();
			break;
		    case 8:
			waitIntro1();
			break;
		    case 9:
			drawIntro2();
			break;
		    case 10:
			waitIntro2();
			break;
		    case 11:
			drawIntro3();
			break;
		    case 12:
			waitIntro3();
			break;
		    default:
			break;
		    }
		repaint();
	    }
    }
    
    public void start()
    {
	if (game==null)
	    {
		game=new Thread(this);
		game.start();
	    }
    }
    
    public void stop()
    {
	if ((game!=null)&&(game.isAlive()))
	    {
		game.stop();
	    }
	game=null;
    }
    
    public boolean keyDown(java.awt.Event e,int key)
    {
	if (gameState==2)
	    {
		switch (key)
		    {
		    case 97:
			dir=1; // A:Left
			break;
		    case 100:
			dir=2; // D:Right
			break;
		    case 107:
			dir=3; // K:Up
			break;
		    case 109:
			dir=4; // M:Down
			break;
		    default:
			break;
		    }
	    }
	else if ((gameState>6)&&(key==32))
	    gameState=0;
	return false;
    }
    
    public boolean keyUp(java.awt.Event e,int key)
    {
	dir=0;
	return false;
    }
    
    public void prepareField()
    {
	int i,j,p,q;
	if (level>effMax)
	    effLevel=effMax;
	else
	    effLevel=level;
	offGraphics.setColor(Color.black);
	offGraphics.fillRect(0,0,mainX,mainY);
	playGraphics.setColor(Color.black);
	playGraphics.fillRect(0,0,playX,playY);
	offGraphics.setColor(Color.lightGray);
	offGraphics.fill3DRect(0,mainY-playY-4,mainX,4,true);
	offGraphics.setColor(Color.white);
	offGraphics.drawString("SCORE:",2,12);
	updateScore(0);
	offGraphics.drawString("LEVEL:  "+(level+1),125,12);
	offGraphics.drawString("SPARE LIVES:",220,12);
	for (i=0;i<lives;i++)
	    offGraphics.drawImage(small[13],300+i*15,-16,this);
	for (i=0;i<(blockX+2)*(blockY+3);i++)
	    playArea[i]=1;
	for (i=1;i<=blockY;i++)
	    for (j=1;j<=blockX;j++)
		playArea[i*(blockX+2)+j]=0;
	playArea[blockX+3]=1; // Make room for start square
	i=0;
	while (i<levIce[effLevel]) // Ice cubes
	    {
		p=1+(int)(m.random()*blockX);
		q=1+(int)(m.random()*blockY);
		if (playArea[q*(blockX+2)+p]==0)
		    {
			playArea[q*(blockX+2)+p]=2;
			playGraphics.drawImage(small[16],
					       (p-1)*30,
					       (q-1)*30,
					       this);
			i++;
		    }
	    }
	i=0;
	while (i<levRock[effLevel]) // Rock
	    {
		p=1+(int)(m.random()*blockX);
		q=1+(int)(m.random()*blockY);
		if (playArea[q*(blockX+2)+p]==0)
		    {
			playArea[q*(blockX+2)+p]=1;
			playGraphics.drawImage(small[14],
					       (p-1)*30,
					       (q-1)*30,
					       this);
			i++;
		    }
	    }
	i=0;
	while (i<5) // Coins
	    {
		p=1+(int)(m.random()*blockX);
		q=1+(int)(m.random()*blockY);
		if (playArea[q*(blockX+2)+p]==0)
		    {
			playArea[q*(blockX+2)+p]=10;
			playGraphics.drawImage(small[24],
					       (p-1)*30,
					       (q-1)*30,
					       this);
			i++;
		    }
	    }
	playArea[blockX+3]=0; // Clear start square
	gameState=1;
	snooze=100;
	counter=0;
	motion[0]=0;
	actors=1;
	coins=0;
	flames=0;
	look[0]=0;
	x[0]=30;
	y[0]=30;
	dx[0]=6;
	dy[0]=6;
	look[0]=2;
	creature[0]=1;
	for (i=0;i<20;i++)
	    ccount[i]=0;
    }
    
    public void showField()
    {
	Graphics saveGraphics;
	saveGraphics=offGraphics.create();
	offGraphics.clipRect(
			     playX/2-(counter*playX/2/30),
			     mainY-playY+playY/2-(counter*playY/2/30),
			     counter*playX/30,
			     counter*playY/30);
	offGraphics.drawImage(playField,0,mainY-playY,this);
	if (counter==30)
	    {
		gameState=2;
		snooze=100;
	    }
	offGraphics=saveGraphics;
    }
    
    public void gameLoop()
    {
	if (flames<levFlame[effLevel])
	    {
		if (x[0]<(playX/2))
		    x[actors]=playX+30;
		else
		    x[actors]=0;
		y[actors]=30*(1+(int)(m.random()*blockY));
		j=(y[actors]/30)*(blockX+2)+x[actors]/30;
		motion[actors]=0;
		dx[actors]=levSpeed[effLevel];
		dy[actors]=levSpeed[effLevel];
		creature[actors]=4;
		if ((playArea[j+1]==0)||(playArea[j-1]==0))
		    {
			actors++;
			flames++;
		    }
	    }
	for (i=0;i<actors;i++)
	    {
		ccount[i]++;
		switch(motion[i])
		    {
		    case 1:
			x[i]-=dx[i];
			break;
		    case 2:
			x[i]+=dx[i];
			break;
		    case 3:
			y[i]-=dy[i];
			break;
		    case 4:
			y[i]+=dy[i];
			break;
		    default:
			break;
		    }
		j=(y[i]/30)*(blockX+2)+x[i]/30;
		switch(creature[i])
		    {
		    case 1: // Penguin
			if ((x[i]%30 == 0)&&(y[i]%30 == 0))
			    motion[i]=0;
			if (motion[i]==0)
			    {
				inFront=playArea[j+sideIX[dir]];
				if ((j+2*sideIX[dir])<0)
				    inFront2=1;
				else
				    inFront2=playArea[j+2*sideIX[dir]];
				if (inFront==0)
				    motion[i]=dir;
				else
				    {
					if ((inFront2==0)&&
					    ((inFront==2)||
					     (inFront==10))) // Push ice block?
					    {
						if (inFront==2)
						    {
							creature[actors]=2;
							look[actors]=16;
						    }
						else
						    {
							creature[actors]=3;
							look[actors]=24;
						    }
						x[actors]=x[i]+coorDx[dir];
						y[actors]=y[i]+coorDy[dir];
						dx[actors]=15;
						dy[actors]=15;
						playGraphics.fillRect
						    (x[actors]-30,
						     y[actors]-30,
						     30,
						     30);
						motion[actors]=dir;
						actors++;
						playArea[j+sideIX[dir]]=0;
					    }
					else if ((inFront>1)&&
						 (inFront<18)) // Crack ice
					    {
						playArea[j+sideIX[dir]]++;
						if (inFront==9) // All cracked?
						    {
							playGraphics.fillRect
							    (x[i]+
							     coorDx[dir]-30,
							     y[i]+
							     coorDy[dir]-30,
							     30,30);
							playArea
							    [j+sideIX[dir]]=0;
							updateScore(5);
						    }
						else if (inFront==17)
						    {
							playGraphics.fillRect
							    (x[i]+
							     coorDx[dir]-30,
							     y[i]+
							     coorDy[dir]-30,
							     30,
							     30);
							playArea
							    [j+sideIX[dir]]=0;
							updateScore(100);
							coins++;
						    }
						else
						    playGraphics.drawImage
							(small[inFront+15],
							 x[i]+coorDx[dir]-30,
							 y[i]+coorDy[dir]-30,
							 this);
					    }
				    }
			    }
			if (motion[i]!=0)
			    look[i]=animP[(motion[i]-1)*4+counter%4];
			for (k=1;k<actors;k++)
			    if (creature[k]==4)
				if (((x[k]-x[i])<20)&&
				    ((x[i]-x[k])<20)&&
				    ((y[k]-y[i])<20)&&
				    ((y[i]-y[k])<20))
				    {
					creature[k]=6;
					x[k]=0;
					y[k]=0;
					motion[k]=0;
					ccount[i]=0;
					dx[i]=0;
					dy[i]=0;
					creature[i]=7;
				    }
			break;
			
		    case 2: // Moving ice block
			if ((x[i]%30 == 0)&&
			    (y[i]%30 == 0)&&
			    (playArea[j+sideIX[motion[i]]]!=0))
			    {
				playArea[j]=2;
				playGraphics.drawImage
				    (small[16],x[i]-30,y[i]-30,this);
				removeActor(i);
			    }
			break;
		    case 3: // Moving frozen coin
			if ((x[i]%30 == 0)&&
			    (y[i]%30 == 0)&&
			    (playArea[j+sideIX[motion[i]]]!=0))
			    {
				playArea[j]=10;
				playGraphics.drawImage
				    (small[24],x[i]-30,y[i]-30,this);
				removeActor(i);
			    }
			break;
		    case 4: // Flame
			look[i]=animF[counter%8];
			if (motion[i]==0)
			    motion[i]=(int)(1+m.random()*4);
			if ((x[i]%30 == 0)&&(y[i]%30 == 0)) // Track penguin
			    {
				if (((x[i]-x[0])<3)&&((x[0]-x[i])<3))
				    {
					if (y[i]>y[0])
					    motion[i]=3;
					else
					    motion[i]=4;
				    }
				else if (((y[i]-y[0])<3)&&((y[0]-y[i])<3))
				    {
					if (x[i]>x[0])
					    motion[i]=1;
					else
					    motion[i]=2;
				    }
				if (playArea[j+sideIX[motion[i]]]!=0)
				    motion[i]=0;
			    }
			for (k=1;k<actors;k++) // Colliding with moving block?
			    if ((creature[k]&254)==2)
				if (((x[k]-x[i])<30)&&
				    ((x[i]-x[k])<30)&&
				    ((y[k]-y[i])<30)&&
				    ((y[i]-y[k])<30))
				    {
					creature[i]=5;
					k=actors;
					look[i]=37;
					motion[i]=0;
					ccount[i]=0;
					updateScore(50);
				    }
			break;
		    case 5: // Flashing "50"
			look[i]=37+(counter&1);
			if (ccount[i]>20)
			    {
				flames--;
				removeActor(i);
			    }
			break;
		    case 6: // Dummy
			break;
		    case 7: // Skeleton
			if (ccount[i]<8)
			    look[i]=39+ccount[i];
			else if (ccount[i]<30)
			    look[i]=47;
			else
			    {
				lives--;
				if (lives<0)
				    gameState=5;
				else
				    {
					actors=1;
					flames=0;
					counter=0;
					dx[i]=6;
					dy[i]=6;
					creature[0]=1;
					look[0]=2;
					offGraphics.setColor(Color.black);
					offGraphics.fillRect(300,0,45,14);
					for (k=0;k<lives;k++)
					    offGraphics.drawImage
						(small[13],300+k*15,-16,this);
				    }
			    }
			break;
		    default:
			break;
		    }
	    }
	if (coins>4)
	    {
		gameState=3;
		updateScore(1000);
		counter=0;
		coins=0;
		offGraphics.drawImage(playField,0,mainY-playY,this);
	    }
    }
    
    public void happyPenguin()
    {
	if (counter>35)
	    {
		level++;
		gameState=4;
		counter=0;
	    }
    }
    
    public void clearField()
    {
	offGraphics.setColor(Color.black);
	offGraphics.fillRect
	    (playX/2-(playX*counter/30),
	     mainY-playY/2-(playY*counter/30),
	     playX*counter/15,
	     playY*counter/15);
	if (counter>14)
	    gameState=0;
    }
    
    public void fixDeath()
    {
	offGraphics.setColor(Color.black);
	offGraphics.fillRect(0,0,mainX,mainY);
	offGraphics.setColor(Color.white);
	offGraphics.drawString("GAME OVER",175,100);
	offGraphics.drawString("You scored "+score,160,130);
	offGraphics.drawImage(small[2],190,150,this);
	counter=0;
	gameState=6;
    }
    
    public void gameOver()
    {
	if (counter>80)
	    gameState=7;
    }
    
    public void drawIntro1()
    {
	level=0;
	score=0;
	lives=3;
	offGraphics.setColor(Color.black);
	offGraphics.fillRect(0,0,mainX,mainY);
	offGraphics.setColor(Color.white);
	offGraphics.drawImage(title,(mainX-224)/2,10,this);
	offGraphics.drawString("ACTORS AND OBJECTS",145,97);
	offGraphics.drawImage(small[2],140,110,this);
	offGraphics.drawString("Pixel Pete, the penguin",180,130);
	offGraphics.drawImage(small[34],120,150,this);
	offGraphics.drawImage(small[32],140,150,this);
	offGraphics.drawString("Evil flames",180,170);
	offGraphics.drawImage(small[16],140,190,this);
	offGraphics.drawString("Ice cube",180,210);
	offGraphics.drawImage(small[14],140,230,this);
	offGraphics.drawString("Solid rock",180,250);
	offGraphics.drawImage(small[24],140,270,this);
	offGraphics.drawString("Frozen gold coin",180,290);
	offGraphics.drawString("Press SPACE to start",138,330);
	counter=0;
	gameState=8;
    }
    
    public void waitIntro1()
    {
	offGraphics.setColor(Color.black);
	offGraphics.fillRect(120,150,50,30);
	offGraphics.drawImage(small[animF[(counter+2)&7]],120,150,this);
	offGraphics.drawImage(small[animF[counter&7]],140,150,this);
	if (counter>70)
	    gameState=9;
    }
    
    public void drawIntro2()
    {
	offGraphics.setColor(Color.black);
	offGraphics.fillRect(0,75,mainX,230);
	offGraphics.setColor(Color.white);
	offGraphics.drawString("HOW TO PLAY",165,97);
	offGraphics.drawImage(small[2],140,110,this);
	offGraphics.drawString("Move up, down, left and right",180,122);
	offGraphics.drawString("with the K, M, A and D keys",180,137);
	offGraphics.drawImage(small[10],70,150,this);
	offGraphics.drawImage(small[16],140,150,this);
	offGraphics.drawString("Walk against ice cubes",180,162);
	offGraphics.drawString("to move them out of the way",180,177);
	offGraphics.drawLine(110,160,136,160);
	offGraphics.drawLine(116,169,136,169);
	offGraphics.drawImage(small[10],80,190,this);
	offGraphics.drawImage(small[18],110,190,this);
	offGraphics.drawImage(small[16],140,190,this);
	offGraphics.drawString("Walk against blocked",180,202);
	offGraphics.drawString("ice cubes to crack them",180,217);
	offGraphics.drawImage(small[28],110,230,this);
	offGraphics.drawImage(small[9],140,230,this);
	offGraphics.drawString("Free the gold coins by",180,242);
	offGraphics.drawString("crushing the ice around them",180,257);
	offGraphics.drawImage(small[9],80,270,this);
	offGraphics.drawImage(small[32],140,270,this);
	offGraphics.drawLine(110,280,126,280);
	offGraphics.drawLine(110,289,130,289);
	offGraphics.drawString("And watch out",180,282);
	offGraphics.drawString("for the flames",180,297);
	gameState=10;
	counter=0;
    }
    
    public void waitIntro2()
    {
	offGraphics.setColor(Color.black);
	offGraphics.drawImage(small[1+(counter % 12)],140,110,this);
	offGraphics.fillRect(140,270,30,30);
	offGraphics.drawImage(small[animF[counter&7]],140,270,this);
	if (counter>80)
	    gameState=11;
    }
    
    public void drawIntro3()
    {
	offGraphics.setColor(Color.black);
	offGraphics.fillRect(0,75,mainX,230);
	offGraphics.setColor(Color.white);
	offGraphics.drawString("SCORING",180,97);
	offGraphics.drawImage(small[10],110,110,this);
	offGraphics.drawImage(small[18],140,110,this);
	offGraphics.drawString("Breaking ice,",180,122);
	offGraphics.drawString("5 points",180,137);
	offGraphics.drawImage(small[33],60,150,this);
	offGraphics.drawImage(small[16],80,150,this);
	offGraphics.drawImage(small[9],140,150,this);
	offGraphics.drawLine(112,160,126,160);
	offGraphics.drawLine(112,169,130,169);
	offGraphics.drawString("Putting out flame",180,162);
	offGraphics.drawString("with ice, 50 points",180,177);
	offGraphics.drawImage(small[10],110,190,this);
	offGraphics.drawImage(small[27],140,190,this);
	offGraphics.drawString("Freeing coin,",180,202);
	offGraphics.drawString("100 points",180,217);
	for (j=0;j<5;j++)
	    offGraphics.drawImage(small[15],100-9*j,230,this);
	offGraphics.drawImage(small[39],140,230,this);
	offGraphics.drawString("Taking all coins and advancing",180,242);
	offGraphics.drawString("to next level, 1000 points",180,257);
	gameState=12;
	counter=0;
    }
    
    public void waitIntro3()
    {
	offGraphics.setColor(Color.black);
	offGraphics.fillRect(60,150,20,30);
	offGraphics.drawImage(small[animF[counter&7]],60,150,this);
	offGraphics.drawImage(small[16],80,150,this);
	if (counter>70)
	    gameState=7;
    }
    
    public void removeActor(int i)
    {
	int j;
	for (j=i;j<actors;j++)
	    {
		x[j]=x[j+1];
		y[j]=y[j+1];
		dx[j]=dx[j+1];
		dy[j]=dy[j+1];
		look[j]=look[j+1];
		motion[j]=motion[j+1];
		creature[j]=creature[j+1];
	    }
	actors--;
    }
    
    public void updateScore(long i)
    {
	score+=i;
	offGraphics.setColor(Color.black);
	offGraphics.fillRect(50,0,60,12);
	offGraphics.setColor(Color.white);
	offGraphics.drawString(String.valueOf(score),50,12);
    }
    
    public void paint(Graphics g)
    {
	g.drawImage(offImage,0,0,this);
    }
    
    public void update(Graphics g)
    {
	int k;
	switch (gameState)
	    {
	    case 2: // Playing
		offGraphics.drawImage(playField,0,mainY-playY,this);
		for (k=0;k<actors;k++)
		    offGraphics.drawImage
			(small[look[k]],
			 x[k]-30,
			 y[k]-30+mainY-playY,
			 this);
		break;
	    case 3:
		offGraphics.drawImage
		    (small[39*(counter&1)],
		     x[0]-30,
		     y[0]-30+mainY-playY,
		     this);
		break;
	    default:
		break;
	    }
	
	paint(g);
    }
}

And that, basically, is it.


Last updated: Jul 31, 2002 by Adrian German for A201