CS 489.02 - Computers and Games - Spring 2009
Update/Render


Loyola College > Department of Computer Science > Dr. James Glenn > CS 489.02 > Examples and Lecture Notes > Update/Render

Some graphics by Josh Wiegand, Kelly Robertson, Amanda Olmsted, and Chris Golon from Fall 2008 CS201

CalaverasCrossingPainter.java and CalaverasCrossingWindow are incomplete. Compiled versions are available in the archive.

CalaverasCrossingApplet.java

/*
<APPLET CODE="CalaverasCrossingApplet.class" WIDTH=450 HEIGHT=600></APPLET>
*/

import javax.swing.*;
import java.awt.*;

/**
 * An applet that displays a game of Calaveras Crossing.
 *
 * @author Jim Glenn
 * @version 0.1 10/7/2008
 * @high 3460 as of 10/8/2008
 */

public class CalaverasCrossingApplet extends JApplet
{
    public void init()
    {
	CalaverasCrossingGame game = new CalaverasCrossingGame();

	Level level = new Level();

	// ADD LANES HERE

	game.startLevel(level);

	CalaverasCrossingPanel panel = new CalaverasCrossingPanel(game);
	CalaverasCrossingControl control = new CalaverasCrossingControl(game, panel);
	panel.addKeyListener(control);

	getContentPane().setLayout(new BorderLayout());
	getContentPane().add(panel, BorderLayout.CENTER);
    }
}

	

CalaverasCrossingControl.java

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * The controller for Calaveras Crossing.  The controller handles the time and
 * the player's input.
 *
 * @author Jim Glenn
 * @version 0.1 10/7/2008
 */

public class CalaverasCrossingControl extends KeyAdapter
{
    /**
     * The game model this controller is connected to.
     */

    private CalaverasCrossingGame game;

    /**
     * The game view this controller is connected to.
     */

    private CalaverasCrossingPanel view;

    /**
     * Whether we have given the panel the focus.
     */

    private boolean gotFocus;

    /**
     * Creates a CalaverasCrossing controller connected to the given game model and
     * view.
     *
     * @param g a CalaverasCrossing game
     * @param p a panel displaynig a CalaverasCrossing game
     */

    public CalaverasCrossingControl(CalaverasCrossingGame g, CalaverasCrossingPanel p)
    {
	game = g;
	view = p;

	gotFocus = false;

	Timer t = new Timer(1000 / 20,
			    new ActionListener() {
				public void actionPerformed(ActionEvent e)
				{
				    timerTick();
				}
			    });
	t.start();
    }

    /**
     * Handles timer events.
     */

    public synchronized void timerTick()
    {
	game.update();
	view.repaint();

	if (!gotFocus)
	    {
		view.requestFocus();
		gotFocus = true;
	    }
    }

    public void keyPressed(KeyEvent e)
    {
	if (e.getKeyCode() == KeyEvent.VK_UP)
	    game.getPlayerFrog().startJump(Frog.UP);
	else if (e.getKeyCode() == KeyEvent.VK_DOWN)
	    game.getPlayerFrog().startJump(Frog.DOWN);
	else if (e.getKeyCode() == KeyEvent.VK_LEFT)
	    game.getPlayerFrog().startJump(Frog.LEFT);
	else if (e.getKeyCode() == KeyEvent.VK_RIGHT)
	    game.getPlayerFrog().startJump(Frog.RIGHT);
    }
}

CalaverasCrossingGame.java

import java.util.*;
import java.awt.*;

/**
 * A Calaveras Crossing game.  A game consists of a level (which contains lanes
 * that in turn contain obstacles and animals) together with the player's
 * frog.
 *
 * @author Jim Glenn
 * @version 0.1 10/7/2008
 */

public class CalaverasCrossingGame
{
    /**
     * Constants for scores
     */

    public static final int FORWARD_JUMP_SCORE = 10;
    public static final int FROG_HOME_SCORE = 50;
    public static final int REMAINING_TIME_SCORE = 10;
    public static final int ESCORT_SCORE = 200;
    public static final int FLY_EATEN_SCORE = 200;
    public static final int LEVEL_COMPLETE_SCORE = 1000;

    /**
     * The amount of time the player has to being a frog home.
     */

    public static final int FROG_TIME = 45;

    /**
     * The time to wait before starting a new frog.
     */

    public static final double NEW_FROG_WAIT = 3.0;

    /**
     * The default number of lives at the start of the game.
     */

    public static final int DEFAULT_LIVES = 3;

    /**
     * This game's current level.
     */

    private Level level;

    /**
     * The player's score.
     */

    private int score;

    /**
     * The time this game was last updated, in milliseconds.  -1 indicates
     * no update has taken place.
     */

    private long lastUpdate;

    /**
     * The number of frogs left in this game.
     */

    private int frogsLeft;

    /**
     * The number of frogs that have reached home during the current level.
     */

    private int frogsHome;

    /**
     * The player's frog.
     */

    private Frog playerFrog;

    public CalaverasCrossingGame()
    {
	level = null;
	lastUpdate = -1;
	playerFrog = new Frog(Color.GREEN.darker(), Lane.SCREEN_WIDTH / 2, 12, FROG_TIME);
	frogsLeft = DEFAULT_LIVES - 1;
	frogsHome = 0;
	score = 0;
    }

    /**
     * Starts the given level in this game.
     *
     * @param l a level
     */

    public void startLevel(Level l)
    {
	if (l == null)
	    throw new IllegalArgumentException("null level");

	level = l;
    }

    /**
     * Returns the player's frog from this game.
     */

    public Frog getPlayerFrog()
    {
	return playerFrog;
    }

    /**
     * Returns an iterator over the lanes in this game's current level.
     *
     * @return an iterator over the lanes in this game's current level
     */

    public Iterator laneIterator()
    {
	return level.laneIterator();
    }

    /**
     * Returns the given lane from this game's current level.
     *
     * @param index a lane index
     */

    public Lane getLane(int index)
    {
	return level.getLane(index);
    }

    /**
     * Updates this game.
     */

    public synchronized void update()
    {
	long t = System.currentTimeMillis();

	if (lastUpdate != -1 && level != null)
	    {
		// compute time since last update and notify everything in
		// this game

		double interval = (t - lastUpdate) / 1000.0;

		level.update(interval);

		playerFrog.update(interval, this);

		if (!playerFrog.isVisible()
		    && frogsHome < 5
		    && playerFrog.getStateTime() >= NEW_FROG_WAIT)
		    {
			playerFrog = new Frog(Color.GREEN.darker(), Lane.SCREEN_WIDTH / 2, 12, FROG_TIME);
		    }
		else if (playerFrog.isDead()
			 && playerFrog.getStateTime() > NEW_FROG_WAIT
			 && frogsLeft > 0)
		    {
			playerFrog = new Frog(Color.GREEN.darker(), Lane.SCREEN_WIDTH / 2, 12, FROG_TIME);
			frogsLeft--;
		    }			
	    }

	lastUpdate = t;
    }

    /**
     * Adds the given amount to the player's score for this game.
     *
     * @param s the amount to add
     */

    public void addScore(int s)
    {
	score += s;
    }

    /**
     * Updates the game statistics to reflect a forward jump.
     */

    public void forwardJump()
    {
	addScore(FORWARD_JUMP_SCORE);
    }

    /**
     * Updates game statistics to reflect the fact that a frog was brought
     * home.
     */

    public void frogHome()
    {
	frogsHome++;
	addScore(FROG_HOME_SCORE);

	addScore((FROG_TIME - (int)(playerFrog.getLifeTime())) * REMAINING_TIME_SCORE);

	if (frogsHome == 5)
	    {
		addScore(LEVEL_COMPLETE_SCORE);
	    }
    }

    /**
     * Returns the number of lives left in this game.
     *
     * @return the number of lives left in this game
     */

    public int countLives()
    {
	return frogsLeft;
    }

    /**
     * Returns the score for the game.
     *
     * @return the score for the game
     */

    public int getScore()
    {
	return score;
    }
}

CalaverasCrossingPainter.java

import java.util.*;
import java.awt.*;

/**
 * A painter for Calaveras Crossing games.
 *
 * @author Jim Glenn
 * @version 0.2 1/8/2009 changed for x-coordinates measured in frog widths
 * @version 0.1 10/7/2008
 */

public class CalaverasCrossingPainter
{
    /**
     * The game this painter paints.
     */

    private CalaverasCrossingGame game;

    /**
     * Creates a painter to paint the given game.
     *
     * @param g a CalaverasCrossing game
     */

    public CalaverasCrossingPainter(CalaverasCrossingGame g)
    {
	game = g;
    }

    /**
     * Paints this painter's game on the given graphics context.
     *
     * @param g the graphics context to paint in
     * @param w the width of the area to paint in
     * @param h the height of the area to paint in
     */

    public void paint(Graphics g, int w, int h)
    {
	// compute the height in pixels of each lane

	int laneHeight = h / Level.NUM_LANES;

	// paint all the lanes
	
	Iterator i = game.laneIterator();
	int laneIndex = 0;
	while (i.hasNext())
	    {
		Lane l = (Lane)(i.next());

		Graphics laneClip = g.create(0,
					     laneIndex * laneHeight,
					     w,
					     laneHeight);

		paintLane(laneClip, w, laneHeight, l);
		laneIndex++;
	    }

	// draw score, time, and remaining frogs

	g.setColor(Color.WHITE);
	g.drawString("SCORE: " + game.getScore() + " TIME: " + (int)(game.getPlayerFrog().getRemainingTime()) + " LIVES: " + game.countLives(), 0, 13 * laneHeight - 2);

	// paint the player's frog

	Frog frog = game.getPlayerFrog();

	// does the clipping region not get rotated along with the
	// coordinate system or is something else wrong here?

	Graphics frogClip;
	if (frog.getDirection() == Frog.UP || frog.getDirection() == Frog.DOWN)
	    {
		frogClip = g.create((int)(w * frog.getX() / Lane.SCREEN_WIDTH),
				    (int)(frog.getY() * laneHeight),
				    (int)(w * (frog.getX() + frog.getWidth()) / Lane.SCREEN_WIDTH),
				    (int)(frog.getHeight() * laneHeight));
	    }
	else
	    {
		frogClip = g.create((int)(w * frog.getX() / Lane.SCREEN_WIDTH),
				    (int)(frog.getY() * laneHeight),
				    (int)(frog.getHeight() * laneHeight),
				    (int)(w * (frog.getX() + frog.getWidth()) / Lane.SCREEN_WIDTH));
	    }

	Graphics2D g2 = (Graphics2D)frogClip;

	if (frog.getDirection() == Frog.LEFT)
	    {
		g2.rotate(-Math.PI / 2);
		g2.translate(-(int)(w * frog.getWidth() / Lane.SCREEN_WIDTH) + 1, 0);
	    }
	else if (frog.getDirection() == Frog.DOWN)
	    {
		g2.rotate(Math.PI);
		g2.translate(-(int)(w * frog.getWidth()/ Lane.SCREEN_WIDTH) + 1, -(int)(frog.getHeight() * laneHeight));
	    }
	else if (frog.getDirection() == Frog.RIGHT)
	    {
		g2.rotate(Math.PI / 2);
		g2.translate(0, -(int)(frog.getHeight() * laneHeight));
	    }

	paintFrog(g2, (int)(w * frog.getWidth() / Lane.SCREEN_WIDTH), (int)(frog.getHeight() * laneHeight), frog);
    }

    /**
     * Paints the given lane on the given graphics context.
     *
     * @param g the graphics context to paint in
     * @param w the width of the area to paint in
     * @param h the height of the area to paint in
     * @param l the lane to paint
     */

    private void paintLane(Graphics g, int w, int h, Lane l)
    {
	// paint the background

	switch (l.getType())
	    {
	    case Lane.ROAD:
		g.setColor(Color.GRAY);
		break;

	    case Lane.GRASS:
		g.setColor(Color.GREEN);
		break;

	    case Lane.RIVER:
		g.setColor(Color.BLUE);
		break;
	    }

	g.fillRect(0, 0, w, h);

	// if you're paying attention to my comments and want to paint some
	// lane markers, this is the place to do it -- the upper left corner
	// of the lane we're currently working on is (0, 0) and the lower
	// right corner is (w - 1, h - 1); some thin white rectangles along
	// the top edge might do the trick

	// paint the obstacles in the lane

	Iterator i = l.obstacleIterator();
	while (i.hasNext())
	    {
		Pair p = (Pair)(i.next());
		Obstacle o = (Obstacle)(p.getFirst());
		double pos = ((Double)(p.getSecond())).doubleValue();

		int obsX = (int)(w * pos / Lane.SCREEN_WIDTH);
		int obsW = (int)(w * o.getWidth() / Lane.SCREEN_WIDTH);

		Graphics2D obstacleClip = (Graphics2D)(g.create(obsX, 0, obsW, h));

		if (l.getVelocity() < 0)
		    {
			/*
			obstacleClip.rotate(Math.PI);
			obstacleClip.translate(-(int)(w * o.getWidth() / Lane.SCREEN_WIDTH), -h);
			*/

			obstacleClip.scale(-1.0, 1.0);
			obstacleClip.translate(-(int)(w * o.getWidth() / Lane.SCREEN_WIDTH), 0);
		    }

		paintObstacle(obstacleClip, obsW, h, o);
	    }
    }
    
    /**
     * Paints the given obstacle.
     *
     * @param g the graphics context to paint in
     * @param w the width to paint in
     * @param h the height to paint in
     * @param o the obstacle to paint
     */

    public void paintObstacle(Graphics g, int w, int h, Obstacle o)
    {
	if (o instanceof Truck)
	    paintTruck(g, w, h, (Truck)o);
	else if (o instanceof Tractor)
	    paintTractor(g, w, h, (Tractor)o);
	else if (o instanceof Car)
	    paintCar(g, w, h, (Car)o);
	else if (o instanceof Racecar)
	    paintRacecar(g, w, h, (Racecar)o);
	else if (o instanceof Log)
	    paintLog(g, w, h, (Log)o);
	else if (o instanceof Turtles)
	    paintTurtles(g, w, h, (Turtles)o);
	else if (o instanceof Home)
	    paintHome(g, w, h, (Home)o);
	else
	    {
		g.setColor(Color.WHITE);
		g.fillRect(0, 0, w, h);
	    }
    }

    /**
     * Paints the given truck.
     * @param g the graphics context to paint in
     * @param w the width to paint in
     * @param h the height to paint in
     * @param t the truck to paint
     */

    public void paintTruck(Graphics g, int w, int h, Truck t)
    {
	paintVehicle(g, w, h, t);
    }

    /**
     * Paints the given truck.
     * @param g the graphics context to paint in
     * @param w the width to paint in
     * @param h the height to paint in
     * @param t the truck to paint
     */

    public void paintTractor(Graphics g, int w, int h, Tractor t)
    {
	paintVehicle(g, w, h, t);
    }

    /**
     * Paints the given car.
     * @param g the graphics context to paint in
     * @param w the width to paint in
     * @param h the height to paint in
     * @param c the car to paint
     */

    public void paintCar(Graphics g, int w, int h, Car c)
    {
	paintVehicle(g, w, h, c);
    }
    
    /**
     * Paints the given truck.
     * @param g the graphics context to paint in
     * @param w the width to paint in
     * @param h the height to paint in
     * @param c the car to paint
     */

    public void paintRacecar(Graphics g, int w, int h, Racecar c)
    {
	paintVehicle(g, w, h, c);
    }

    /**
     * Paints the given vehicle.
     * @param g the graphics context to paint in
     * @param w the width to paint in
     * @param h the height to paint in
     * @param v the vehicle to paint
     */

    public void paintVehicle(Graphics g, int w, int h, Vehicle v)
    {
        g.setColor(Color.BLUE.darker());
	g.fillRect(1, 1, w - 2, h - 2);
    }

    /**
     * Paints the given log.
     * @param g the graphics context to paint in
     * @param w the width to paint in
     * @param h the height to paint in
     * @param l the log to paint
     */

    public void paintLog(Graphics g, int w, int h, Log l)
    {
	g.setColor(Color.BROWN);
	g.fillRect(0, 0, w, h);
    }

    /**
     * Paints the given group of turtles.
     * @param g the graphics context to paint in
     * @param w the width to paint in
     * @param h the height to paint in
     * @param t the turtles to paint
     */

    public void paintTurtles(Graphics g, int w, int h, Turtles t)
    {
	int turtleWidth = w / t.getCount();

	for (int i = 0; i < t.getCount(); i++)
	    {
		Graphics turtleClip = g.create(turtleWidth * i,
					       0,
					       turtleWidth,
					       h);

		paintTurtle(turtleClip, turtleWidth, h, t);
	    }
    }

    /**
     * Paints a single turtle within a group
     * @param g the graphics context to paint in
     * @param w the width to paint in
     * @param h the height to paint in
     * @param t the group of turtles the one to paint is in
     */

    public void paintTurtle(Graphics g, int w, int h, Turtles t)
    {
	g.setColor(Color.RED);
	g.fillOval(0, 0, w, h);
    }

    /**
     * Paints a frog home.
     * @param g the graphics context to paint in
     * @param w the width to paint in
     * @param h the height to paint in
     * @param home the home to paint
     */

    public void paintHome(Graphics g, int w, int h, Home home)
    {
	g.setColor(Color.BLUE);
	g.fillRect(0, 0, w, h);
    }

    /**
     * Paints a frog.
     * @param g the graphics context to paint in
     * @param w the width to paint in
     * @param h the height to paint in
     * @param frog the frog to paint
     */

    public void paintFrog(Graphics g, int w, int h, Frog frog)
    {
	g.setColor(frog.getColor());
	g.fillOval(0, 0, w, h);
    }
}

CalaverasCrossingPanel.java

import java.awt.*;
import javax.swing.*;

/**
 * A panel showing the main part of the screen for Calaveras Crossing.
 *
 * @author Jim Glenn
 * @version 0.2 1/8/2009 changed for x-coordinates measured in frog widths
 * @version 0.1 10/7/2008
 */

public class CalaverasCrossingPanel extends JPanel
{
    /**
     * The game this panel displays.
     */

    private CalaverasCrossingGame game;

    /**
     * The painter for the game displayed in this panel.
     */

    private CalaverasCrossingPainter painter;

    /**
     * Creates a new panel displaying the given game.
     *
     * @param g a CalaverasCrossing game
     */

    public CalaverasCrossingPanel(CalaverasCrossingGame g)
    {
	game = g;
	painter = new CalaverasCrossingPainter(g);
    }

    /**
     * Paints this game.
     *
     * @param g the graphics context to paint in
     */

    public void paint(Graphics g)
    {
	// determine the area to paint in based on whether the aspect
	// ratio of this panel is greater than or less than the aspect
	// ratio of the game

	double aspect = (double)Lane.SCREEN_WIDTH / Level.NUM_LANES;

	int w, h;

	if ((double)getWidth() / getHeight() > aspect)
	    {
		h = getHeight();
		w = (int)(h * aspect);
	    }
	else
	    {
		w = getWidth();
		h = (int)(w / aspect);
	    }

	Graphics clipRegion = g.create((getWidth() - w) / 2,
				       (getHeight() - h) / 2,
				       w,
				       h);

	painter.paint(g, w, h);
    }
}

CalaverasCrossingWindow.java

/**
 * A window that displays a game of Calaveras Crossing.
 *
 * @author Jim Glenn
 * @version 0.2 1/8/2009 changed for x-coordinates measured in frog widths
 * @version 0.1 10/7/2008
 * @high 2910 as of 10/8/2008
 */

import javax.swing.*;
import java.awt.*;

public class CalaverasCrossingWindow extends JFrame
{
    public CalaverasCrossingWindow()
    {
	super("Calaveras Crossing");

	CalaverasCrossingGame game = new CalaverasCrossingGame();

	Level level = new Level();

	// ADD LANES HERE

	game.startLevel(level);

	CalaverasCrossingPanel panel = new CalaverasCrossingPanel(game);
	CalaverasCrossingControl control = new CalaverasCrossingControl(game, panel);

	if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != null)
	    {
		System.out.println(KeyboardFocusManager.getCurrentKeyboardFocusManager());
		KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner().addKeyListener(control);
	    }

	getContentPane().setLayout(new BorderLayout());
	getContentPane().add(panel, BorderLayout.CENTER);

	setSize(400, 300);
	setVisible(true);
    }

    public static void main(String[] args)
    {
	JFrame win = new CalaverasCrossingWindow();
	win.setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
}

	

Car.java

/**
 * A Calaveras Crossing car.
 *
 * @author Jim Glenn
 * @version 0.2 1/8/2009 changed for x-coordinates measured in frog widths
 * @version 0.1 10/7/2008
 */

public class Car extends Vehicle
{
    /**
     * The width of a car, in frog widths.
     */

    private static final double CAR_WIDTH = 1.25;

    /**
     * Creates a new car.
     */

    public Car()
    {
	super(CAR_WIDTH);
    }
}

Floater.java

/**
 * A Calaveras Crossing floating obstacle.
 *
 * @author Jim Glenn
 * @version 0.1 10/7/2008
 */

public class Floater extends Obstacle
{
    /**
     * Creates a new floating obstacle of the given width.
     *
     * @param w a positive <CODE>double</CODE>
     */

    public Floater(double w)
    {
	super(w);
    }

    /**
     * Determines if this floating obstacle is above water.
     *
     * @return true if and only if this floating obstacle is above water
     */

    public boolean isAboveWater()
    {
	return true;
    }
}

Frog.java

import java.awt.*;

/**
 * A frog.  This can be the player's frog or a friendly frog the player
 * can take home.
 *
 * @author Jim Glenn
 * @version 0.2 1/8/2009 changed for x-coordinates measured in frog widths
 * @version 0.1 10/7/2008
 */

public class Frog
{
    /**
     * Direction constants.
     */

    public static final int UP = 0;
    public static final int DOWN = 1;
    public static final int LEFT = 2;
    public static final int RIGHT = 3;

    /**
     * Deltas for directions.
     */

    private static final int[] dx = {0, 0, -1, 1};
    private static final int[] dy = {-1, 1, 0, 0};

    /**
     * The direction this frog is facing.
     */

    private int facingDir;

    /**
     * The vertical position of this frog, in whole lanes.  This will
     * be the lane this frog was last in.
     */

    private int y;

    /**
     * The horizontal position of this frog, in frog widths.
     */

    private double x;

    /**
     * The color of this frog.
     */

    private Color color;

    /**
     * The maximum lifespan of this frog.  This frog will die if
     * it is not home before this time expires.
     */

    private double maxTime;

    /**
     * The total lifespan of this frog.
     */

    private double lifeTime;

    /**
     * State constants.
     */

    public static final int DEAD = 0;
    public static final int SITTING = 1;
    public static final int JUMPING = 2;
    public static final int HOME = 3;

    /**
     * The state of this frog.  The state can be dead, jumping, or sitting.
     */

    private int state;

    /**
     * The amount of time this frog has spent in its current state.
     */

    private double stateTime;

    /**
     * The time it takes this frog to complete a jump, in seconds.
     */

    private double jumpTime;

    /**
     * The default jumping speed of a frog in seconds.
     */

    public static final double DEFAULT_JUMP_TIME = 0.2;

    /**
     * The horizontal distance covered in a single jump, measured in frog
     * widths.
     */

    public static final double JUMP_DISTANCE = 1.0;

    /**
     * The width of a frog, in frog widths.  Provided for consistency with
     * older implementation.
     */

    public static final double FROG_WIDTH = 1.0;

    /**
     * Creates a new frog of the given color at the given location.
     */

    public Frog(Color c, double startX, int startY, int t)
    {
	color = c;
	x = startX;
	y = startY;
	facingDir = UP;
	setState(SITTING);
	jumpTime = DEFAULT_JUMP_TIME;
	maxTime = t;
	lifeTime = 0.0;
    }

    /**
     * Returns the color of this frog.
     *
     * @return the color of this frog
     */

    public Color getColor()
    {
	return color;
    }

    /**
     * Updates this frog.
     *
     * @param t the time since the last update, in seconds
     * @param game the game this frog is part of
     */

    public void update(double t, CalaverasCrossingGame game)
    {
	// update timers

	if (state != HOME)
	    {
		lifeTime += t;
	    }

	stateTime += t;

	if (state == JUMPING && stateTime >= jumpTime)
	    {
		// end of jump

		if (facingDir == UP)
		    game.forwardJump();

		setState(SITTING);
		
		x += dx[facingDir] * JUMP_DISTANCE;
		y += dy[facingDir];
	    }
	
	Lane l = game.getLane(y);
	
	// check for floating on logs
	
	if (state == SITTING)
	    {
		if (y == 0)
		    {
			// check for home
			
			Home h = l.findHome(x, x + getWidth());
			
			if (h != null && !h.isOccupied())
			    {
				h.setOccupied();
				setState(HOME);
				game.frogHome();
			    }
			else
			    {
				setState(DEAD);
			    }
		    }
		else
		    {
			// float or drown if in river
			
			if (l.getType() == Lane.RIVER)
			    {
				if (l.hasFloaterAt(x + getWidth() / 3.0)
				    && l.hasFloaterAt(x + getWidth() * 2 / 3.0))
				    x -= l.getVelocity() * t;
				else
				    {
					setState(DEAD);
				    }
			    }
			
			// check for collisions

			if (l.hasCollision(x, x + getWidth()))
			    {
				setState(DEAD);
			    }
		    }
	    }
	else if (state == JUMPING)
	    {
		if (facingDir == UP || facingDir == DOWN)
		    {
			Lane jumpLane = game.getLane(y + dy[facingDir]);
			
			if (stateTime < jumpTime * 0.25)
			    {
				if (l.hasCollision(x, x + getWidth()))
				    {
					setState(DEAD);
				    }
			    }
			else if (stateTime > jumpTime * 0.75)
			    {
				if (jumpLane.hasCollision(x, x + getWidth()))
				    {
					setState(DEAD);
				    }
			    }
			else
			    {
				if (jumpLane.hasCollision(x, x + getWidth())
				    || l.hasCollision(x, x + getWidth()))
				    {
					setState(DEAD);
				    }
			    }
		    }
		else
		    {
			// horizontal jump
			
			if (l.hasCollision(getX(), getX() + getWidth()))
			    {
				setState(DEAD);
			    }
		    }
	    }
	
	if ((getX() < 0.0 || getX() + getWidth() > Lane.SCREEN_WIDTH) && state != DEAD)
	    {
		setState(DEAD);
	    }

	if (lifeTime > maxTime && state != HOME && state != DEAD)
	    {
		setState(DEAD);
	    }
    }

    /**
     * Returns the current x position of this frog.
     *
     * @return the x position of this frog
     */

    public double getX()
    {
	if (state != JUMPING || facingDir == UP || facingDir == DOWN)
	    {
		return x;
	    }
	else if (facingDir == LEFT)
	    {
		if (stateTime < jumpTime * 0.75)
		    return x - JUMP_DISTANCE * (stateTime / (jumpTime * 0.75));
		else
		    return x - JUMP_DISTANCE;
	    }
	else /* if (facingDir == RIGHT) */
	    {
		if (stateTime < jumpTime * 0.25)
		    return x;
		else
		    return x + JUMP_DISTANCE * (stateTime - 0.25 * jumpTime) / (jumpTime * 0.75);
	    }
    }

    /**
     * Returns the current y position of this frog.
     *
     * @return the y position of this frog
     */

    public double getY()
    {
	if (state != JUMPING || facingDir == LEFT || facingDir == RIGHT)
	    {
		return y;
	    }
	else if (facingDir == UP)
	    {
		if (stateTime < jumpTime * 0.75)
		    {
			return y - (stateTime / (jumpTime * 0.75));
		    }
		else
		    return y - 1.0;
	    }
	else /* if (facingDir == DOWN) */
	    {
		if (stateTime < jumpTime * 0.25)
		    {
			return y;
		    }
		else
		    {
			return y + (stateTime - 0.25 * jumpTime) / (jumpTime * 0.75);
		    }
	    }
    }

    /**
     * Returns the direction this frog is facing.
     *
     * @return the direction this frog is facing
     */

    public int getDirection()
    {
	return facingDir;
    }

    /**
     * Returns the current painting width of this frog in units of
     * screen widths.  "Width" here means the size across the frog
     * (that is, perpendicular to the direction it is facing).
     */

    public double getWidth()
    {
	return FROG_WIDTH;

	/*
	if (state != JUMPING || facingDir == UP || facingDir == DOWN)
	    { 
		return FROG_WIDTH;
	    }
	else
	    {
		if (stateTime < jumpTime * 0.25)
		    {
			// beginning of jump

			return FROG_WIDTH * (1.0 + 1.0 / 3.0 * stateTime / (jumpTime * 0.25));
		    }
		else if (stateTime > jumpTime * 0.75)
		    {
			// end of jump

			return FROG_WIDTH * (1.0 + 1.0 / 3.0 * (jumpTime - stateTime) / (jumpTime * 0.25));
		    }
		else
		    {
			return FROG_WIDTH * 4.0 / 3.0;
		    }
	    }
	*/
    }

    /**
     * Returns the current painting height of this frog in units of
     * lane height.  "Height" here means along the frog (that is, in the
     * direction it is facing).
     *
     * @return the painting height of this frog
     */
    
    public double getHeight()
    {
	if (state != JUMPING /*|| facingDir == LEFT || facingDir == RIGHT*/)
	    {
		return 1.0;
	    }
	else
	    {
		if (stateTime < jumpTime * 0.25)
		    {
			// beginning of jump

			return (1.0 + 1.0 / 3.0 * stateTime / (jumpTime * 0.25));
		    }
		else if (stateTime > jumpTime * 0.75)
		    {
			// end of jump

			return (1.0 + 1.0 / 3.0 * (jumpTime - stateTime) / (jumpTime * 0.25));
		    }
		else
		    {
			return 4.0 / 3.0;
		    }
	    }
    }

    /**
     * Determines if this frog is alive.
     *
     * @return true if and only if this frog is alive
     */

    public boolean isDead()
    {
	return (state == DEAD);
    }

    /**
     * Determines if this frog is visible.  A frog is visible if it is not home.
     *
     * @return true if and only if this frog is visible
     */

    public boolean isVisible()
    {
	return (state != HOME);
    }

    /**
     * Determines if this frog is moving.
     *
     * @return true if and only if this frog is in the middle of a jump
     */

    public boolean isMoving()
    {
	return (state == JUMPING);
    }

    /**
     * Starts this frog a-jumpin' in the given direction.
     *
     * @param dir one of the direction constants
     */

    public void startJump(int dir)
    {
	if (state == SITTING && (y < 12 || dir != DOWN))
	    {
		facingDir = dir;
		setState(JUMPING);
	    }
    }

    /**
     * Sets the state of this frog.
     *
     * @param s one of the state contants
     */

    public void setState(int s)
    {
	state = s;
	stateTime = 0.0;
    }

    /**
     * Returns how long this frog has been in its current state.
     *
     * @return how long this frog has been in its current state
     */

    public double getStateTime()
    {
	return stateTime;
    }

    /**
     * Returns how long this frog has been alive.
     *
     * @return how long this frog has been alive
     */

    public double getLifeTime()
    {
	return lifeTime;
    }

    /**
     * Returns how much time this frog has left to live
     *
     * @return how much time this frog has left to live
     */

    public double getRemainingTime()
    {
	return Math.max(0.0, maxTime - lifeTime);
    }
}

Home.java

/**
 * A home for a frog.
 *
 * @author Jim Glenn
 * @version 0.2 1/8/2009 changed for x-coordinates measured in frog widths
 * @version 0.1 10/7/2008
 */

public class Home extends Obstacle
{
    /**
     * The width of a home, measured in frog widths.
     */

    public static final double HOME_WIDTH = 1.5;
    
    /**
     * A flag that indicates whether this home is occupied by a frog.
     */

    private boolean occupied;

    /**
     * Creates a new unoccupied home.
     */

    public Home()
    {
	super(HOME_WIDTH);
	occupied = false;
    }

    /**
     * Determines if this home is occupoied.
     *
     * @return true if and only if this home is occupied
     */

    public boolean isOccupied()
    {
	return occupied;
    }

    /**
     * Sets this home to occupied.
     */

    public void setOccupied()
    {
	occupied = true;
    }
}

Lane.java

import java.util.*;

/**
 * A horizontal lane of traffic.  "Traffic" in this context can mean vehicles,
 * logs, or other obstacles.  Lanes can be grass, roadway, or river.
 *
 * @author Jim Glenn
 * @version 0.2 1/8/2009 changed for x-coordinates measured in frog widths
 * @version 0.1 10/7/2008
 */

public class Lane
{
    /**
     * Constants for lane types.
     */

    public static final int ROAD = 0;
    public static final int GRASS = 1;
    public static final int RIVER = 2;

    /**
     * The default visible width of a lane, in frog widths.
     */

    public static final int SCREEN_WIDTH = 15;

    /**
     * The maximum velocity of a lane, in frog widths per second.
     */

    private static final double VELOCITY_MAX = 7.5;

    /**
     * The width of this lane, as a proportion of the screen width.
     */

    private double width;

    /**
     * The type of this lane.
     */

    private int type;

    /**
     * The position within this lane of the left edge of the screen.
     */

    private double position;

    /**
     * The velocity of this lane, in screen widths per second.  Positive
     * velocities indicate right-to-left motion; negative velocities indicate
     * left-to-right motion.
     */

    private double velocity;

    /**
     * The obstacles in this lane.
     */

    private List obstacles;

    /**
     * Creates an empty grass lane one screen wide.  The lane will
     * be stationary and will start at the left edge of the screen.
     */

    public Lane()
    {
	this(GRASS, SCREEN_WIDTH);
    }

    /**
     * Creates an empty grass lane of the given width.  The lane will
     * be stationary and will start at the left edge of the screen.
     *
     * @param w a <CODE>double</CODE> at least 1.0
     */

    public Lane(double w)
    {
	this(ROAD, w);
    }

    /**
     * Creates an empty lane of the given type.  The lane will be stationary
     * and will start at the left edge of the screen.
     *
     * @param t one of the type constants
     * @param w a <CODE>double</CODE> at least 1.0
     */

    public Lane(int t, double w)
    {
	if (w < SCREEN_WIDTH)
	    throw new IllegalArgumentException("Width must be at least "
					       + SCREEN_WIDTH + ": "
					       + w);

	if (t < ROAD || t > RIVER)
	    throw new IllegalArgumentException("Invalid lane type: " + t);

	width = w;
	type = t;
	velocity = 0.0;
	position = 0.0;
	obstacles = new ArrayList();
    }

    /**
     * Returns the type of this lane.
     *
     * @return the type of this lane
     */

    public int getType()
    {
	return type;
    }

    /**
     * Returns the velocity of this lane.
     *
     * return the velocity of this lane
     */

    public double getVelocity()
    {
	return velocity;
    }

    /**
     * Sets the velocity of this lane.  The velocity is givenin screen widths
     * per second, with positive values for leftward movement and negative
     * values for rightward movement.
     *
     * @param v the new speed of this lane
     * @throws IllegalArgumentException if the magnitude of v is greater than
     * the maximum allowable
     */

    public void setVelocity(double v)
    {
	if (Math.abs(v) > getMaximumVelocity())
	    {
		throw new IllegalArgumentException("invalid velocity: " + v);
	    }

	velocity = v;
    }

    /**
     * Returns the maximum velocity for any lane.
     *
     * @return the maximum velocity
     */

    public static double getMaximumVelocity()
    {
	return VELOCITY_MAX;
    }

    /**
     * Creates a river lane with evenly spaced logs.
     * The number of logs is determined by the argument.  The spaces
     * between the logs will be the same size as the logs.  The width
     * of the lane will be exactly one screen.
     *
     * @param n a positive integer
     * @return a lane with n logs
     */

    public static Lane makeLogLane(int n)
    {
	if (n <= 0)
	    throw new IllegalArgumentException("n must be positive: " + n);

	Lane l = new Lane(RIVER, SCREEN_WIDTH);

	double logWidth = (double)SCREEN_WIDTH / (2 * n);

	for (int i = 0; i < n; i++)
	    l.addObstacle(new Log(logWidth), logWidth * i * 2);

	return l;
    }

    /**
     * Adds the given obstacle to this lane at the given position.
     *
     * @param o an obstacle compatible with this lane
     * @param p a position within this lane
     *
     * @throws IllegalArgumentException if the obstacle is not appropriate
     * for this lane or will not fit at the given position
     */

    public void addObstacle(Obstacle o, double p)
    {
	if (type == GRASS && !(o instanceof Home))
	    throw new IllegalArgumentException("Only homes allowed on grass");
	else if (type == ROAD && !(o instanceof Vehicle))
	    throw new IllegalArgumentException("Only vehicles allowed on road");
	else if (type == RIVER && !(o instanceof Floater))
	    throw new IllegalArgumentException("Only floating obstacles allowed in river");

	if (p < 0.0 || p + o.getWidth() > width)
	    throw new IllegalArgumentException("Obstacle does not fit in lane:"
					       + "(" + p + ", " + (p + o.getWidth()) + ") "
					       + width);

	obstacles.add(new Pair(o, new Double(p)));
    }

    /**
     * Updates this lane and everything in it.
     *
     * @param t the time since the last update, in seconds
     */

    public void update(double t)
    {
	// update position

	position += t * velocity;

	if (position >= width)
	    position -= (int)(position / width) * width;
	else if (position < 0.0)
	    position += ((int)(-position / width) + 1) * width;

	// update obstacles in this lane

	Iterator i = obstacles.iterator();
	while (i.hasNext())
	    {
		Pair p = (Pair)(i.next());
		Obstacle o = (Obstacle)(p.getFirst());

		o.update(t);
	    }

	// update animals in this lane

	// check for appearance of new animals
    }

    /**
     * Finds the home overlapping the given position in this lane.
     *
     * @param x1 the left edge of the area to check for overlap with a home
     * @param x2 the right edge of the area to check for overlap with a home
     * @return the home overlapping that area, or <CODE>null</CODE>
     */

    public Home findHome(double x1, double x2)
    {
	// we take advantage of the fact that the home lane doesn't move
	// so we don't have to worry about wraparound

	Iterator i = obstacles.iterator();
	while (i.hasNext())
	    {
		Pair p = (Pair)(i.next());
		Obstacle o = (Obstacle)(p.getFirst());
		
		if (o instanceof Home)
		    {
			double homePos = ((Double)(p.getSecond())).doubleValue();
			if (x1 >= homePos && x2 <= homePos + o.getWidth())
			    return (Home)o;
		    }
	    }
	return null;
    }

    /**
     * Determines if this lane has a log at the given position.
     *
     * @param x the x position, relative to the edge of the screen
     * @return true if and only if this lane has a log at that position
     */

    public boolean hasFloaterAt(double x)
    {
	if (type != RIVER)
	    return false;

	x += position;
	if (x > width)
	    x -= width;

	Iterator i = obstacles.iterator();
	while (i.hasNext())
	    {
		Pair p = (Pair)(i.next());
		Obstacle o = (Obstacle)(p.getFirst());
		double obstaclePos = ((Double)(p.getSecond())).doubleValue();

		if (x >= obstaclePos
		    && x <= obstaclePos + o.getWidth()
		    && o instanceof Floater
		    && ((Floater)o).isAboveWater())
		    return true;
	    }
	return false;
    }

    /**
     * Determines if there is a collision in this lane between the
     * object between the given points and a lethal obstacle.
     * Positions are given relative to the edge of the screen.
     *
     * @param x1 the left edge of the area to check for collisions in
     * @param x2 the right edge of the area to check for collisions in
     */

    public boolean hasCollision(double x1, double x2)
    {
	return hasCollisionLaneCoordinates(x1 + position, x2 + position);
    }

    /**
     * Determines if there is a collision in this lane between the
     * object between the given points and a lethal obstacle.
     * Positions are given relative to the edge of this lane
     *
     * @param x1 the left edge of the area to check for collisions in
     * @param x2 the right edge of the area to check for collisions in
     */

    private boolean hasCollisionLaneCoordinates(double x1, double x2)
    {
	if (x1 > width)
	    {
		x1 -= width;
		x2 -= width;
	    }

	if (x2 > width)
	    {
		return (hasCollisionLaneCoordinates(x1, width)
			|| hasCollisionLaneCoordinates(0.0, x2 - width));
	    }
	else
	    {
		Iterator i = obstacles.iterator();
		while (i.hasNext())
		    {
			Pair p = (Pair)(i.next());
			Obstacle o = (Obstacle)(p.getFirst());
			double obsLeft = ((Double)(p.getSecond())).doubleValue();
			double obsRight = obsLeft + o.getWidth();
			
			if (o.isLethal()
			    && ((x1 < obsLeft && x2 > obsRight)
				|| (x1 > obsLeft && x1 < obsRight)
				|| (x2 > obsLeft && x2 < obsRight)))
			    {
				return true;
			    }
		    }
		return false;
	    }
    }
	

    /**
     * Returns an iterator over all the visible obstacles in this lane.
     * Obstacles will be visible if any portion of them is currently
     * positioned between 0.0 and the width of the screen.
     * Obstacles may be iterated over
     * twice if they are visible at both edges of the screen.
     * The iterator will return (obstacle, position) pairs, where the
     * position is relative to the edge of the screen.
     *
     * @return an iterator over this lane's obstacles
     */

    public Iterator obstacleIterator()
    {
	// create a list of visible obstacles

	List visible = new LinkedList();

	// get position within this lane of the left and right edges
	// of the screen -- we must do the wraparound carefully:
	// screenLeft + visibleWidth - width may come out >
	// screenLeft, in which case we think there's only a tiny
	// sliver visible and nothing gets painted

	double screenLeft = position;
	double screenRight = screenLeft + SCREEN_WIDTH;
	if (screenRight > width)
	    screenRight = screenLeft + (SCREEN_WIDTH - width);

	Iterator i = obstacles.iterator();
	while (i.hasNext())
	    {
		Pair p = (Pair)(i.next());
		Obstacle o = (Obstacle)(p.getFirst());
		double obsLeft = ((Double)(p.getSecond())).doubleValue();
		double obsRight = obsLeft + o.getWidth();

		if (screenLeft < screenRight)
		    {
			// no wraparound -- obstacle is visible exactly once
			// if either end is on screen

			if ((obsLeft > screenLeft && obsLeft < screenRight)
			    || (obsRight > screenLeft && obsRight < screenRight))
			    visible.add(new Pair(o, new Double(obsLeft - screenLeft)));
		    }
		else
		    {
			// wrapround -- obstacle is entirely visible
			// if its left is > screen left, visible on the
			// left edge if its right is > screen left
			// and (possibly also) on the right edge if
			// its left edge < screen right

			if (obsLeft > screenLeft)
			    visible.add(new Pair(o, new Double(obsLeft - screenLeft)));
			else
			    {
				if (obsRight > screenLeft)
				    visible.add(new Pair(o, new Double(obsLeft - screenLeft)));
				if (obsLeft < screenRight)
				    visible.add(new Pair(o, new Double(obsLeft + width - screenLeft)));
			    }
		    }
	    }

	// return an iterator over the list

	return visible.iterator();
    }
}

Level.java

import java.util.*;

/**
 * A Calaveras Crossing level.  Each Calaveras Crossing level has 13
 * lanes, including the starting lane and the home lane.
 *
 * @author Jim Glenn
 * @version 0.2 1/8/2009 changed for x-coordinates measured in frog widths
 * @version 0.1 10/7/2008
 */

public class Level
{
    /**
     * The number of lanes per level, including the starting lane and the
     * home lane.
     */

    public static final int NUM_LANES = 13;

    /**
     * The number of homes in a level.
     */

    public static final int NUM_HOMES = 5;

    /**
     * The lanes in this level.
     */

    private Lane[] lanes;

    /**
     * Create a new level with 12 empty grass lanes and the home lane.
     */

    public Level()
    {
	lanes = new Lane[NUM_LANES];

	// create empty lanes of grass
	
	for (int l = 1; l < NUM_LANES; l++)
	    {
		lanes[l] = new Lane();
	    }

	// create home lane

	Lane homeLane = new Lane();
	
	// compute home spacing so that homes are evenly spaced at
	// positions that are integer multiples of frog widths;
	// this assumes that the screen is at least 5 frogs wide.

	int spacing = 0;
	while (NUM_HOMES * (spacing + 1) - 1 <= Lane.SCREEN_WIDTH)
	    {
		spacing++;
	    }

	// compute position of 1st home that centers things

	double firstHome = (Lane.SCREEN_WIDTH / 2 - (Home.HOME_WIDTH - 1.0) / 2) - (NUM_HOMES / 2 * spacing);

	for (int h = 0; h < NUM_HOMES; h++)
	    {
		homeLane.addObstacle(new Home(), firstHome + h * spacing);
	    }

	lanes[0] = homeLane;
    }

    /**
     * Adds the given lane to this level at the given position.  Lanes
     * are numbered from top to bottom starting with 0 for the niche lane.
     *
     * @param l a lane
     * @param p lane position
     */

    public void addLane(Lane l, int p)
    {
	if (l == null)
	    throw new IllegalArgumentException("null lane");

	if (p < 0 || p >= NUM_LANES)
	    throw new IllegalArgumentException("Invalid lane index: " + p);

	if (p == NUM_LANES - 1)
	    throw new IllegalArgumentException("Can't replace home lane: " + p);

	lanes[p] = l;
    }

    /**
     * Returns the number of lanes in this level.  In the current
     * implementation all levels have 13 lanes.
     *
     * @return the number of lanes in this level
     */

    public int countLanes()
    {
	return NUM_LANES;
    }

    /**
     * Returns the lane at the given position in this level.
     *
     * @param p a lane index
     */

    public Lane getLane(int p)
    {
	if (p < 0 || p >= NUM_LANES)
	    throw new IllegalArgumentException("Invalid lane index: " + p);

	return lanes[p];
    }

    /**
     * Updates this level and everything in it.
     *
     * @param t the time since the last update
     */

    public void update(double t)
    {
	for (int l = 0; l < NUM_LANES; l++)
	    lanes[l].update(t);
    }

    /**
     * Returns an iterator over this level's lanes.
     *
     * @return an iterator over this level's lanes
     */

    public Iterator laneIterator()
    {
	return new LaneIterator();
    }

    /**
     * An iterator over this level's lanes.
     */

    private class LaneIterator implements Iterator
    {
	/**
	 * The index of the lane this iterator is currently positioned before.
	 */

	private int index;

	/**
	 * Creates an iterator positioned before this level's first lane.
	 */

	public LaneIterator()
	{
	    index = 0;
	}

	/**
	 * Determines if this iterator has a next lane to iterate over.
	 */

	public boolean hasNext()
	{
	    return (index < NUM_LANES);
	}
	
	/**
	 * Returns the next lane this iterator iterates over and positions
	 * this iterator just after that lane.
	 */

	public Object next()
	{
	    if (!hasNext())
		throw new IllegalStateException("no next lane: " + index);

	    index++;

	    return lanes[index - 1];
	}

	/**
	 * Unsupported.
	 *
	 * @throws UnsupportedOperationException
	 */

	public void remove()
	{
	    throw new UnsupportedOperationException();
	}
    }
}

Log.java

/**
 * A Calaveras Crossing log.
 *
 * @author Jim Glenn
 * @version 0.1 10/7/2008
 */

public class Log extends Floater
{
    /**
     * Creates a new obstacle of the given width.
     *
     * @param w a positive <CODE>double</CODE>
     */

    public Log(double w)
    {
	super(w);
    }
}

Obstacle.java

/**
 * A Calaveras Crossing obstacle.  Obstacles include vehicles, logs,
 * and turtles.  Otters, snakes, and other frogs are not obstacles.
 *
 * @author Jim Glenn
 * @version 0.1 10/7/2008
 */

public class Obstacle
{
    /**
     * The width of this obstacle, measured in frog widths.
     */

    private double width;

    /**
     * Creates a new obstacle of the given width.
     *
     * @param w a positive <CODE>double</CODE>
     */

    public Obstacle(double w)
    {
	if (w <= 0.0)
	    throw new IllegalArgumentException("Width must be positive:" + w);

	width = w;
    }

    /**
     * Returns the width of this obstacle.
     *
     * @return the width of this obstacle
     */

    public double getWidth()
    {
	return width;
    }

    /**
     * Updates this obstacle.  The default implementation does nothing.
     *
     * @param t the time sine the last update, in seconds
     */

    public void update(double t)
    {
    }

    /**
     * Determines if this obstacle is lethal to the touch.
     */

    public boolean isLethal()
    {
	return false;
    }
}

Pair.java

/**
 * An ordered pair of objects.
 *
 * @author Jim Glenn
 * @version 0.1 8/8/2003
 */

public class Pair
{
    /**
     * The two objects in this pair.
     */

    Object x, y;

    /**
     * Constructs the given ordered pair.
     *
     * @param first the first component of the new pair     
     * @param second the second component of the new pair
     */

    public Pair(Object first, Object second)
    {
	x = first;
	y = second;
    }

    /**
     * Returns the first component of this pair.
     *
     * @return the first component of this pair
     */

    public Object getFirst()
    {
	return x;
    }

    /**
     * Returns the second component of this pair.
     *
     * @return the second component of this pair
     */

    public Object getSecond()
    {
	return y;
    }

    /**
     * Sets the first component of this pair to the given value.
     *
     * @param obj the new first component
     */

    public void setFirst(Object obj)
    {
	x = obj;
    }

    /**
     * Sets the second component of this pair to the given value.
     *
     * @param obj the new second component
     */

    public void setSecond(Object obj)
    {
	y = obj;
    }

    /**
     * Returns a printable representation of this pair.
     *
     * @return a printable representation of this pair
     */

    public String toString()
    {
	return "(" + x + ", " + y + ")";
    }
}

Racecar.java

/**
 * A Calaveras Crossing racecar.
 *
 * @author Jim Glenn
 * @version 0.2 1/8/2009 changed for x-coordinates measured in frog widths
 * @version 0.1 10/7/2008
 */

public class Racecar extends Vehicle
{
    /**
     * The width of a racecar, in frog widths.
     */

    private static final double RACECAR_WIDTH = 1.25;

    /**
     * Creates a new racecar.
     */

    public Racecar()
    {
	super(RACECAR_WIDTH);
    }
}

Tractor.java

/**
 * A Calaveras Crossing tractor.
 *
 * @author Jim Glenn
 * @version 0.2 1/8/2009 changed for x-coordinates measured in frog widths
 * @version 0.1 10/7/2008
 */

public class Tractor extends Vehicle
{
    /**
     * The width of a tractor, in frog widths.
     */

    private static final double TRACTOR_WIDTH = 1.25;

    /**
     * Creates a new tractor.
     */

    public Tractor()
    {
	super(TRACTOR_WIDTH);
    }
}

Truck.java

/**
 * A Calaveras Crossing truck.
 *
 * @author Jim Glenn
 * @version 0.2 1/8/2009 changed for x-coordinates measured in frog widths
 * @version 0.1 10/7/2008
 */

public class Truck extends Vehicle
{ 
    /**
     * The width of a truck, in frog widths.
     */

    private static final double TRUCK_WIDTH = 2.5;

    /**
     * Creates a new truck.
     */

    public Truck()
    {
	super(TRUCK_WIDTH);
    }
}

Turtles.java

/**
 * A group of Calaveras Crossing turtles.
 *
 * @author Jim Glenn
 * @version 0.2 1/8/2009 changed for x-coordinates measured in frog widths
 * @version 0.1 10/7/2008
 */

public class Turtles extends Floater
{
    /**
     * The width of a single turtle, in frog widths.
     */

    private static final double TURTLE_WIDTH = 1.0;

    /**
     * The number of turtles in this group.
     */
    
    private int num;

    /**
     * A flag that is true if and only if this group of turtles dives.
     */

    private boolean diving;

    /**
     * The length of time these turtles spend above water and then
     * underwater if they are diving.
     */

    private double diveTime;

    /**
     * The time within the current dive cycle.  Times in the first half are
     * above water and times in the second half are below.
     */

    private double cycleTime;

    /**
     * Creates a new group with the given number of non-diving turtles in it.
     *
     * @param n a positive integer
     */

    public Turtles(int n)
    {
	super(TURTLE_WIDTH * n);

	diving = false;
	diveTime = 1.0;
	cycleTime = 0.0;
	num = n;
    }

    /**
     * Returns the number of turtles in this group.
     *
     * @return the number of turtles in this group
     */

    public int getCount()
    {
	return num;
    }

    /**
     * Sets the diving status of this group of turtles.
     *
     * @param flag true to set these turtles to diving
     */

    public void setDiving(boolean flag)
    {
	diving = flag;
    }

    /**
     * Determines whether these turtles dive.  This method
     * determines if these turtles have a dive cycle, not whether they
     * are currently in the dive phase of their cycle.  To determine if
     * these turtles are currently underwater use <CODE>isAboveWater</CODE>
     * or <TT>getDepth</TT>.
     *
     * @return true if and only if these turtles dive
     */

    public boolean canDive()
    {
	return diving;
    }

    /**
     * Sets the dive time for these turtles.  The dive time is
     * 1.0 by default.  There is no effect on non-diving turtles.
     *
     * @param t a positive number
     */

    public void setDiveTime(double t)
    {
	if (t <= 0.0)
	    throw new IllegalArgumentException("Dive time must be positive: "
					       + t);

	diveTime = t;
    }

    /**
     * Updates this obstacle.  The default implementation does nothing.
     *
     * @param t the time sine the last update, in seconds
     */

    public void update(double t)
    {
	// update cycle time

	cycleTime += t;
	
	// and move to beginning of next cycle if necessary (this formula
	// takes into account cases where an update is not performed
	// for an entire cycle

	if (cycleTime > 2 * diveTime)
	    cycleTime -= (int)(cycleTime / (2 * diveTime)) * (2 * diveTime);
    }

    /**
     * Determines if these turtles are above water.
     *
     * @return true if and only if these turtles are currently above water
     */

    public boolean isAboveWater()
    {
	return (!diving || cycleTime <= diveTime);
    }

    /**
     * Returns the current depth of these turtles.  The value returned will
     * be between -1.0 and 1.0 for diving turtles and 1.0 for non-diving turtles.
     *
     * @return a number between 1.0 and 1.0
     */

    public double getDepth()
    {
	if (diving)
	    {
		return Math.sin(Math.PI * cycleTime / diveTime);
	    }
	else
	    {
		return 1.0;
	    }
    }
}

Vehicle.java

/**
 * A CalaverasCrossing vehicle.
 *
 * @author Jim Glenn
 * @version 0.1 10/7/2008
 */

public class Vehicle extends Obstacle
{
    /**
     * Creates a new vehicle of the given width.
     *
     * @param w a positive <CODE>double</CODE>
     */

    public Vehicle(double w)
    {
	super(w);
    }

    /**
     * Determines if this vehicle is lethal to the touch.
     */

    public boolean isLethal()
    {
	return true;
    }
}
This code can also be downloaded from the files
CalaverasCrossingApplet.java, CalaverasCrossingControl.java, CalaverasCrossingGame.java, CalaverasCrossingPainter.java, CalaverasCrossingPanel.java, CalaverasCrossingWindow.java, Car.java, Floater.java, Frog.java, Home.java, Lane.java, Level.java, Log.java, Obstacle.java, Pair.java, Racecar.java, Tractor.java, Truck.java, Turtles.java, and Vehicle.java.