runtime error accessing ArrayList

These 2 classes are basically the classes from the book developing games in java, without the fullscreen stuff. They are supposed to create an animation. They both compile fine but when I run I get an error message, an no animation, paint() is only called once. Here’s the error message:

Exception in thread “main” java.lang.IndexOutOfBoundsException: Index: 4, Size: 4
at java.util.ArrayList.RangeCheck(ArrayList.java:546)
at java.util.ArrayList.get(ArrayList.java:321)
at Animation.getFrame(Animation.java:60)
at Animation.update(Animation.java:43)
at AnimationTest1.animationLoop(AnimationTest1.java:58)
at AnimationTest1.run(AnimationTest1.java:44)
at AnimationTest1.main(AnimationTest1.java:10)

I think this means the program is trying to access frame 4 when there is none. Right? I just can’t get my brain around this. Any help would be great!


import java.awt.Image;
import java.util.ArrayList;
 
/*	this class manages a serie of images (frames) and the amount of time to 
	display each frame */
	
public class Animation {
	
	private ArrayList frames;
	private int currFrameIndex;
	private long animTime;
	private long totalDuration;
	
	//creates a new, empty animation, calls start().
	public Animation() {
		frames = new ArrayList();
		totalDuration = 0;
		start();
	}
	
	//adds an image to the animation, + how long to display the image.
	public synchronized void addFrame(Image image, long duration) {
		totalDuration += duration;
		frames.add(new AnimFrame(image, totalDuration));
	}
	
	//starts this animation over from the beginning .
	public synchronized void start() {
		animTime = 0;
		currFrameIndex = 0;
	}
	
	//updates this animations current frame if necessary
	public synchronized void update(long elapsedTime) {
		if (frames.size() >1) {
			animTime += elapsedTime;
			
			if (animTime >= totalDuration) {
				animTime = animTime % totalDuration;
				currFrameIndex = 0;
			}
			
			while (animTime > getFrame(currFrameIndex).endTime) {
				currFrameIndex ++;
			}
		}
	}
	
	//gets the animations current frame. null if none availible.
	public synchronized Image getImage() {
		if (frames.size() == 0) {
			return null;
		}
		else {
			return getFrame(currFrameIndex).image;
		}
	}
	
	private AnimFrame getFrame(int i) {
		return (AnimFrame)frames.get(i);
	}
	
	private class AnimFrame {
		
		Image image;
		long endTime;
		
		public AnimFrame(Image image, long endtime) {
			this.image = image;
			this.endTime = endTime;
		}
	}
}


import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
 
public class AnimationTest1 {
 
	public static void main (String args[]) {		
		
		AnimationTest1 test = new AnimationTest1();
		test.run();
	}
	
	private static final long DEMO_TIME = 5000;
	
	private JFrame frame;
	private Animation anim;
	
	public void loadImages() {
		//load images
		Image player1 = loadImage("gif1.png");
		Image player2 = loadImage("gif2.png");
		Image player3 = loadImage("gif3.png");
		//create animation
		anim = new Animation();
		anim.addFrame(player1, 200);
		anim.addFrame(player2, 200);
		anim.addFrame(player3, 200);
		anim.addFrame(player2, 200);
	}
	
	//loads image with ImageIcon
	private Image loadImage(String filename) {
		return new ImageIcon(filename).getImage();
	}
	
	public void run() {
		try {
			frame = new JFrame();
			frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			frame.setSize(300, 300);
			frame.setVisible(true);
			
			loadImages();
			animationLoop();
		}
		finally {
		}
	}
	
	public void animationLoop() {
		long startTime = System.currentTimeMillis();
		long currTime = startTime;
		
		while (currTime - startTime < DEMO_TIME) {
			long elapsedTime = System.currentTimeMillis() - currTime;
			currTime += elapsedTime;
			
			anim.update(elapsedTime);
			//getContentpane.getgraphics??!!
			Graphics g = frame.getGraphics();
			draw(g);
			g.dispose();
			
			try {
				Thread.sleep(20);
			}
			catch (InterruptedException ex) {}
		}
	}
	
	//draw the animation
	public void draw(Graphics g) {
		g.setColor(Color.white);
		g.fillRect(0, 0, 300, 300);		
		g.drawImage(anim.getImage(), 0, 0, null);
	}
}

Basically, yes. You’re trying access fifth frame (it’s index is four - 'cause indexes start from 0).

After a brief look at the code I’ve found that the loop in update() is a bit weired. Try this hotfix it might help.

//updates this animations current frame if necessary
	public synchronized void update(long elapsedTime) {
		if (frames.size() >1) {

			...

			while (animTime > getFrame(currFrameIndex).endTime) {
				currFrameIndex ++;
				if (currFrameIndex == frames.size) break; // Hotfix
			}
		}
	}

I thought that was never supposed to happen because of the

 
	animTime = animTime % totalDuration;

I left the Animation class untouched so that would mean the code in the book is wrong. Or I made a mistake copying. I think the fault is somewhere in my test class.

oh by the way your hotfix gave a compile error. I added something similar, It got rid of the error message, but paint is still only called once, before the images are loaded.

oh by the way your hotfix gave a compile error

Oh, sure: it’s frames.sizeb[/b]

Look at your stack trace. It clearly shows that runtime exception happens in Animation.getFrame(Animation.java:60).

First of all, thanks for your replies so far!!

I added the (now working) hotfix but I still get the same runtime error. Which sort of surprised me. I looked at getFrame(), but all the methods depend on eachother so the fault could be anywhere, I think.