// MicroNibbles - Nibbles in 150 Zeilen - (C)2002 Jörg Mensmann
import java.awt.*;
import java.awt.event.*;
import java.awt.image.PixelGrabber;
public class MicroNibbles extends java.applet.Applet implements Runnable, KeyListener {
    Thread thread = null;                     // Timer-Thread
    Image offscreen, backgroundimg;           // Bitmaps
    Graphics offgraphics;                     // Backbuffer-Graphic
    Font bigFont, mediumFont, smallFont;      // Schriftarten
    int direction, snake_len, snake_num, counter = 0, level = 1;
    boolean inGame, showCrash, hasWon, gameOver; // Spiellogik
    int[] snake_x, snake_y;      // Positionen der Einzelteile der Schlange
    int[][] field;               // Spielfeld
    final int FeldX = 64, FeldY = 40, // Größe der Spielfeldes
	EMPTY = 0, OBSTACLE = 1, SNAKE = 2, NUMBER = 3, // für Spielfeld
	NORTH = 0, EAST = 1, SOUTH = 2, WEST = 3;       // Richtungen
    public void init() {
	backgroundimg = getImage(getDocumentBase(), getParameter("bg"));
	bigFont = new Font("TimesRoman,Times", Font.BOLD, 30);
        mediumFont = new Font("TimesRoman,Times", 0, 20);     
        smallFont = new Font("Courier New,Courier,Helvetica", 0, 15); 
        snake_x = new int[70]; 
	snake_y = new int[70];
        field = new int[FeldX + 1][FeldY + 1];
	addKeyListener(this);
    }
    public void start() {
	if (thread == null) {
	    thread = new Thread(this);
	    thread.start();
	}
    }
    public void stop() { thread = null; }
    public void run() {          // Run-Methode des Thread
        while (thread != null) {
	    try { Thread.sleep(80); } catch (InterruptedException e){}
	    counter++;
	    if (inGame) moveSnake();
	    repaint();
        }
        thread = null;
    }
    void newNumber() { // Zahl irgendwo auf dem Spielfeld einfügen
        int x, y;
        do {
            x = (int)(Math.random() * (FeldX - 2)) + 1;
            y = (int)(Math.random() * (FeldY - 2)) + 1;
        } while (GetField(x, y) != EMPTY); // Leeres Feld suchen
        SetField(x, y, NUMBER);
    }
    int GetField(int x, int y) { return field[x][y]; }
    void SetField(int x, int y, int w) { field[x][y] = w; }
    void moveSnake() {
        SetField(snake_x[snake_len], snake_y[snake_len], EMPTY); // Ende der Schlange entfernen
        for (int i=snake_len; i >= 2; i--) { // Positionen weitersetzen
            snake_x[i] = snake_x[i - 1];
            snake_y[i] = snake_y[i - 1];
        }
        switch (direction) {  // Kopf neu setzen bzw. Crash
          case NORTH: if (snake_y[1] > 0) snake_y[1]--; else crash(); break;
          case EAST:  if (snake_x[1] < FeldX - 1) snake_x[1]++; else crash(); break;
          case SOUTH: if (snake_y[1] < FeldY - 1) snake_y[1]++; else crash(); break;
          case WEST:  if (snake_x[1] > 0) snake_x[1]--; else crash(); break;
        }
	if (GetField(snake_x[1], snake_y[1]) == NUMBER) { // Nummer?
            if (--snake_num < 1) { // Level gewonnen?
		inGame = !(hasWon = true); 
		gameOver = (getParameter("level" + (++level)) == null);		    
		if (gameOver) level = 1; 
	    } else {
		 newNumber(); // Neue Nummer und Länge der Schlange erhöhen
		 for (int i=snake_len + 1; i <= (snake_len + 10 - snake_num); i++) {
		     snake_x[i] = snake_x[snake_len];
		     snake_y[i] = snake_y[snake_len];
		 }
		 snake_len += 10 - snake_num;
            }
        }
        if ((GetField(snake_x[1], snake_y[1]) != EMPTY) && (GetField(snake_x[1], snake_y[1]) != NUMBER)) crash(); 
	  else SetField(snake_x[1], snake_y[1], SNAKE);
    }
    void crash () { showCrash = true; inGame = false; }
    public synchronized void update(Graphics g) {
        if (offscreen == null) { // Erster Durchlauf -> Backbuffer erzeugen
            offscreen = createImage(getSize().width, getSize().height);
            offgraphics = offscreen.getGraphics();
        }
	offgraphics.drawImage(backgroundimg, 0, 0, null); // Hintergrund
        if ((inGame) || (showCrash)) {
	    offgraphics.setColor(Color.red);  
	    for (int x=0; x <= FeldX; x++)        // Hindernisse
		for (int y=0; y <= FeldY; y++) 
		    if (GetField(x, y) == OBSTACLE) offgraphics.fillRect(x*10, y*10, 10, 10);
	    if (!showCrash || counter % 2 == 0) { // Wenn gecrashed dann blinkend darstellen
		offgraphics.setColor(Color.green); 
		for (int x=0; x <= FeldX; x++)    // Schlange 
		    for (int y=0; y <= FeldY; y++)
			if (GetField(x, y) == SNAKE) offgraphics.fillRect(x*10, y*10, 10, 10);
	    }
            offgraphics.setFont(smallFont);
            offgraphics.setColor(Color.white);
            for (int x=0; x <= FeldX; x++)        // Zahl
                for (int y=0; y <= FeldY; y++) 
                    if (GetField(x, y) == NUMBER) offgraphics.drawString("" + snake_num, x*10, y*10 + 10);
        }
        if (!inGame) {        // Nicht im Spiel -> Text anzeigen
            offgraphics.setColor(Color.black);
            offgraphics.setFont(bigFont);
            offgraphics.drawString("MicroNibbles", 10, 35);
            offgraphics.setColor(Color.blue);
            offgraphics.setFont(bigFont);
	    if (counter % 2 == 0 && showCrash) offgraphics.drawString("Crash!", 10, 75);
  	      else if ((counter / 7) % 2 == 0 && hasWon) 
                offgraphics.drawString(gameOver ? "You win! - Game Over" : "Level complete!", 10, 75);
	    offgraphics.setFont(mediumFont);
	    offgraphics.drawString("Press SPACE to begin!", 10, 120);
	}
        g.drawImage(offscreen, 0, 0, null);  // Backbuffer auf den Screen
    }   
    public void keyPressed(KeyEvent e) {
        if (!inGame) {
	    if (e.getKeyCode() == KeyEvent.VK_SPACE) initGame();
	} else 
        switch (e.getKeyCode()) {
	  case KeyEvent.VK_UP:    if (direction != SOUTH) direction = NORTH; break;
	  case KeyEvent.VK_DOWN:  if (direction != NORTH) direction = SOUTH; break;
	  case KeyEvent.VK_RIGHT: if (direction != WEST) direction = EAST; break;
	  case KeyEvent.VK_LEFT:  if (direction != EAST) direction = WEST; break;
	}
    }
    public void keyTyped(KeyEvent e) { }
    public void keyReleased(KeyEvent e) { }
    void initGame() { 
	int[] pixels = new int[FeldX * FeldY];
	Image levelimg = getImage(getDocumentBase(), getParameter("level" + level)); 
	PixelGrabber pg = new PixelGrabber(levelimg, 0, 0, FeldX, FeldY, pixels, 0, FeldX);
	try { pg.grabPixels(); } catch (InterruptedException e) { }
	for (int x=0; x < FeldX; x++)   // Level wird abhängig von
          for (int y=0; y < FeldY; y++) // der Level-Grafik aufgebaut
	    SetField(x, y, (pixels[x + y*FeldX] != pixels[0]) ? OBSTACLE : EMPTY);
        direction = 1; snake_num = 9; snake_len = 6; 
        for (int i=1; i <= snake_len; i++) {
	    snake_x[i] = 20 - i;
	    snake_y[i] = 4;
	    SetField(20 - i, 4, SNAKE);
        }
        newNumber();
        inGame = !(showCrash = hasWon = gameOver = false);
    }
}
