//
// JWorm
//
// Copyright 1/1997 by Frank Buss
//

import java.applet.Applet;
import java.awt.*;

public class JWorm extends Applet implements Runnable {

	private float cellX[];
	private float cellY[];
	private int startIndex;
	private int endIndex;
	private int cellSize;
	private boolean inGame;

	private int length; // number of worm-cells
	private float dir; // direction from 0 to 2pi
	private float nextX; // Position in front
	private float nextY; // of the head

	private int maxPizzaR; // maximum pizza radius
	private int minPizzaR; // minimum pizza radius
	private int pizzaR; // radius of the pizza
	private int pizzaX; // Position of
	private int pizzaY; // the pizza

	private Image iBuffer; // double-buffer
	private Graphics gBuffer; // graphics for double-buffer
	private Image iBuffer2; // double-buffer
	private Graphics gBuffer2; // graphics for double-buffer

	private int mouseX; // mouse position when the
	private int mouseY; // user press the button
	private Thread animation; // for animation
	private int width; // playfield width
	private int height; // playfield height
	private Color foreground; // color of worm
	private Color background; // color of playfield
	private int wormR; // radius of one Cell of the worm
	private int wormStep; // distance between two cells
	private int delay; // animation speed
	private int growCount; // number of cells to grow in animation
	private float pi = (float) Math.atan(1) * 4;
	private int[][] field; // memory-field to check where the worm is
	private int[] wormX; // all x coord. for one worm-cell
	private int[] wormY; // all y coord. for one worm-cell
	private int count; // current cell number for field
	private int pos; // number of x and y coords in wormX and wormY
	private int bend; // bend-factor for the worm in degree

	private Image images[] = new Image[4];
	private Image jworm;

	private int pizzaImageX;
	private int pizzaImageY;
	private int pizzaImageNo = -1;

	private Button newGameButton = new Button("Neues Spiel starten");
	private Button helpButton = new Button("Spielregeln");

	// Bild holen und warten, bis es geladen ist
	private Image loadImage(String imageName) {
		// Bild holen
		Image image = getImage(getDocumentBase(), imageName);

		// ein MediaTracker-Objekt anlegen und das Bild davon kontrollieren lassen
		MediaTracker tracker = new MediaTracker(this);
		tracker.addImage(image, 0);

		// solange warten, bis das Bild vollständig geladen ist
		try {
			tracker.waitForID(0);
		} catch (InterruptedException e) {
		}

		// Referenz auf das Bild zurückliefern
		return image;
	}

	// clear one cell in the graphic
	private void clearCell(float xp, float yp) {
		gBuffer.setColor(background);
		gBuffer2.setColor(background);
		int x0 = (int) xp;
		int y0 = (int) yp;
		for (int i = 0; i < pos; i++) {
			int x = wormX[i] + x0;
			int y = wormY[i] + y0;
			gBuffer.drawLine(x, y, x, y);
			gBuffer2.drawLine(x, y, x, y);
			field[x][y] = 0;
		}
	}

	// draw one cell in the graphic
	private void drawCell(float xp, float yp) {
		if (count == 0)
			count = 255;
		else
			count--;
		gBuffer.setColor(foreground);
		gBuffer2.setColor(foreground);
		int x0 = (int) xp;
		int y0 = (int) yp;
		if (x0 <= wormR
			|| x0 >= width - wormR
			|| y0 <= wormR
			|| y0 >= height - wormR)
			stopGame();
		else {
			boolean newGame = false;
			for (int i = 0; i < pos; i++) {
				int x = wormX[i] + x0;
				int y = wormY[i] + y0;
				gBuffer.drawLine(x, y, x, y);
				gBuffer2.drawLine(x, y, x, y);
				if (field[x][y] != 0 && Math.abs(field[x][y] - count) > 5)
					newGame = true;
				field[x][y] = count;
			}
			if (newGame)
				stopGame();
		}
	}

	// calculate the next position in front of the head
	private void setNext() {
		int s = endIndex;
		if (++endIndex == cellSize)
			endIndex = 0;
		cellX[endIndex] = cellX[s] + (float) Math.cos(dir) * wormStep;
		cellY[endIndex] = cellY[s] + (float) Math.sin(dir) * wormStep;
	}

	// add one cell in front of the worm and make it the new head
	private void growWorm() {
		length++;
		setNext();
		drawCell(cellX[endIndex], cellY[endIndex]);
	}

	// move the worm one cell
	private void moveWorm() {
		clearCell(cellX[startIndex], cellY[startIndex]);
		startIndex++;
		if (startIndex == cellSize)
			startIndex = 0;
		setNext();
		drawCell(cellX[endIndex], cellY[endIndex]);
	}

	// return a random number between a and b
	private int myRandom(int a, int b) {
		return (int) (Math.random() * (b - a) + a);
	}

	// set the Pizza position and draw it
	private void drawPizza() {
		pizzaR = 50;
		pizzaX = myRandom(pizzaR + 5, width - pizzaR - 20);
		pizzaY = myRandom(pizzaR + 20, height - pizzaR + 5);
		gBuffer.drawImage(
			images[myRandom(0, 4)],
			pizzaX - pizzaR,
			pizzaY - pizzaR,
			this);
	}

	// clear the old pizza and make a new one
	private void newPizza() {
		gBuffer.drawImage(iBuffer2, 0, 0, this);
		int x0 = pizzaX - pizzaR;
		int y0 = pizzaY - pizzaR;
		drawPizza();
	}

	// start animation
	public void start() {
		if (animation == null) {

			// new Thread for animation
			animation = new Thread(this);
			animation.start();
		}
	}

	// stop animation
	public void stop() {
		if (animation != null) {
			animation.stop();
			animation = null;
		}
	}

	// move the worm
	public void run() {
		while (true) {
			try {
				animation.sleep(delay);
			} catch (Exception e) {
			}

			if (inGame) {
				// calculate the differnce to the mouse-position
				float dx = mouseX - cellX[endIndex];
				float dy = mouseY - cellY[endIndex];

				// calculate the angle of the line mouse to worm-head
				float d = 0;
				if (dx != 0) {
					d = (float) Math.atan(dy / dx);
					if (dx < 0 && dy < 0)
						d += pi;
					if (dy < 0 && dx >= 0)
						d += 2 * pi;
					if (dx < 0 && dy >= 0)
						d += pi;
				}

				// change the current direction to the new direction
				// if the difference is less than 'bend' degree
				// if the difference is greater than 'bend' degree
				// than change the current direction in 'bend' degree steps
				// to become the new direction
				float delta = Math.abs(d - dir);
				if (delta < pi / 180 * bend)
					dir = d;
				else {
					if (delta > pi) {
						if (dir > pi)
							d += 2 * pi;
						else
							d -= 2 * pi;
					}
					if (d > dir) {
						if ((dir += pi / 180 * bend) >= 2 * pi)
							dir -= 2 * pi;
					} else {
						if (d < dir) {
							if ((dir -= pi / 180 * bend) < 0)
								dir += 2 * pi;
						}
					}
				}

				// test pizza-hit and set a new pizza, if the worm hit a pizza
				dx = cellX[endIndex] - pizzaX;
				dy = cellY[endIndex] - pizzaY;
				if (Math.sqrt(dx * dx + dy * dy) - wormR - 1 < pizzaR) {
					growCount += pizzaR;
					newPizza();
				}

				// let the worm grow
				if (growCount > 0) {
					growWorm();
					growCount--;
				} else
					moveWorm();
			}

			repaint();
		}
	}

	private void stopGame() {
		inGame = false;
		newGameButton.setEnabled(true);
	}

	// init everything for a new game
	private void newGame() {

		// clear buffer and draw frame
		gBuffer.setColor(background);
		gBuffer.fillRect(0, 0, width, height);
		gBuffer.setColor(foreground);
		gBuffer.fillRect(0, 0, width - 1, 2);
		gBuffer.fillRect(width - 2, 0, 2, height - 1);
		gBuffer.fillRect(0, 0, 2, height - 1);
		gBuffer.fillRect(0, height - 2, width - 1, 2);
		gBuffer.drawImage(jworm, 0, height, this);

		gBuffer2.setColor(background);
		gBuffer2.fillRect(0, 0, width, height);
		gBuffer2.setColor(foreground);
		gBuffer2.fillRect(0, 0, width - 1, 2);
		gBuffer2.fillRect(width - 2, 0, 2, height - 1);
		gBuffer2.fillRect(0, 0, 2, height - 1);
		gBuffer2.fillRect(0, height - 2, width - 1, 2);
		gBuffer2.drawImage(jworm, 0, height, this);

		// create a worm of the length 1
		cellX[0] = width / 2;
		cellY[0] = height / 2;
		startIndex = 0;
		endIndex = 0;
		length = 0;

		// init worm-data for 16 cells growing
		growCount = 16;
		dir = 0;

		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				field[x][y] = 0;
			}
		}

		// draw a initial pizza
		drawPizza();

		newGameButton.setEnabled(false);
		inGame = true;
	}

	public void init() {

		setLayout(new BorderLayout());
		Panel p = new Panel(new GridLayout(1, 0));
		p.add(newGameButton);
		p.add(helpButton);
		add(BorderLayout.SOUTH, p);

		// init variables
		foreground = Color.black;
		background = new Color(0xc0c0c0);
		setBackground(background);
		images[0] = loadImage("banan.gif");
		images[1] = loadImage("brocoli.gif");
		images[2] = loadImage("apple.gif");
		images[3] = loadImage("mushrom.gif");
		jworm = loadImage("jworm.gif");
		wormR = 4;
		wormStep = 3;
		delay = 20;
		bend = 15;

		// init double-buffer
		width = size().width;
		height = size().height;
		iBuffer = createImage(width, height);
		iBuffer2 = createImage(width, height);
		height -= 100;
		gBuffer = iBuffer.getGraphics();
		gBuffer2 = iBuffer2.getGraphics();

		// init cell array
		cellSize = width * height;
		cellX = new float[cellSize];
		cellY = new float[cellSize];

		field = new int[width][height];
		wormX = new int[wormR * wormR * 4];
		wormY = new int[wormR * wormR * 4];
		for (int x = -wormR; x < wormR; x++) {
			for (int y = -wormR; y < wormR; y++) {
				if (x * x + y * y <= wormR * wormR) {
					wormX[pos] = x;
					wormY[pos] = y;
					pos++;
				}
			}
		}

		// start the first round
		newGame();
		stopGame();
	}

	public boolean mouseMove(Event e, int x, int y) {
		super.mouseMove(e, x, y);
		mouseX = x;
		mouseY = y;
		return true;
	}

	public void paint(Graphics g) {
		super.paint(g);
		gBuffer.setColor(Color.white);
		int h = 13;
		int w = 318;
		gBuffer.fillRect(w, height + h, 209, 56);
		gBuffer.setColor(Color.red);
		String n = String.valueOf(length);
		for (int i = 0; i < n.length(); i++) {
			paint(w + 18 + i * 30, height + h + 11, n.charAt(i) - '0', 18, 4);
		}
		g.drawImage(iBuffer, 0, 0, this);
	}

	public void update(Graphics g) {
		paint(g);
	}

	private final static int digits[] =
		{ 95, 5, 118, 117, 45, 121, 123, 69, 127, 125 };

	private final static int lines[] =
		{
			1,
			1,
			1,
			2,
			0,
			1,
			0,
			2,
			1,
			0,
			1,
			1,
			0,
			0,
			0,
			1,
			0,
			2,
			1,
			2,
			0,
			1,
			1,
			1,
			0,
			0,
			1,
			0 };

	/**
	 * @param g Graphics for paint.
	 * @param x x position.
	 * @param y y position.
	 * @param digit The digit to paint.
	 * @param m Multiplicatior for one line.
	 * @param w width / 2 for one line.
	 */
	public void paint(int x, int y, int digit, int m, int w) {
		digit = digits[digit];
		for (int b = 1, i = 0; i < 7; i++, b *= 2) {
			if ((b & digit) == b) {
				int j = i * 4;
				int x0 = lines[j] * m - w;
				int y0 = lines[j + 1] * m - w;
				int x1 = lines[j + 2] * m + w;
				int y1 = lines[j + 3] * m + w;
				gBuffer.fillRect(x0 + x, y0 + y, x1 - x0, y1 - y0);
			}
		}
	}

	// testet die Schaltflächen
	public boolean handleEvent(Event evt) {
		// nur näher testen, wenn die Nachricht ACTION_EVENT aufgetreten war
		if (evt.id == Event.ACTION_EVENT) {
			if (evt.target == newGameButton) {
				newGame();
			} else if (evt.target == helpButton) {
				JWormHelpDialog.showIt(
					"JWorm Spielregeln",
					"Ziel des Spiels ist es, das Obst und das\n"
						+ "Gemüse mit dem Wurm aufzusammeln, um\n"
						+ "dadurch den Punktestand zu\n"
						+ "erhöhen. \n"
						+ "\n"
						+ "Sie steuern den Wurm, indem\n"
						+ "Sie mit der Maus in die Richtung zeigen,\n"
						+ "zu der Wurm sich bewegen soll.\n");
			}
		}

		// Status der Nachrichten-Behandlungs-Methode der Basisklasse zurückliefern
		return super.handleEvent(evt);
	}
}

