/*
Datei............: Aufg0734.java
Projekt..........: Einführung in die Java-Programmierung
Erstellt.........: 05.11.97, Guido Krüger
Geändert.........: --
Aufgabe..........: Musterlösung zu Aufgabe 07.34
Kommentare.......:

Dieses File implementiert eine komplette, textbasierte Variante
von Conway's Game-Of-Life. Die Arbeit teilen sich drei Klassen:

- Aufg0734    erledigt die Benutzersteuerung und hält die 
              vordefinierten Muster vor
- GameOfLife  dient zur eigentlichen Simulation des Spiels und
              enthält das Spielbrett und den Code zur Berechnung 
              der Nachfolgegenerationen.
- LifePattern ist ein kleiner Parser, der es erlaubt, Muster in
              pseudografischer Darstellung vorzugeben und zur 
              Definition einer Stellung auf dem Brett zu verwenden.

Für das Verständnis der Aufgabe ist es eigentlich nur nötig, den
Code in der Klasse GameOfLife zu verstehen. Der Oberflächencode
in Aufg0734 ist straightforward und verwendet die Routinen der
Klasse GameOfLife. Die Klasse LifePattern läuft gewissermaßen
außer Konkurrenz; sie dient lediglich dazu, einige bekannte Life-
Muster für eigene Experimente zur Verfügung zu stellen.

Das Hauptprogramm stellt folgende Eingabefunktionen zur Verfügung:

N = Anlegen eines neuen Spielplans.
G = Berechnen der nächsten Generation.
Z = Berechnen der nächsten zehn Generationen.
D = Dauerhaftes berechnen der Folgegenerationen (kann nur mit STRG-C
    abgebrochen werden).
S = Eine Zelle zum Leben erwecken.
R = Eine Zelle sterben lassen.
M = Anlegen eines neuen Spielplans und eingeben eines von 13 ausgewählten
    Life-Mustern.
E = Programmende.
*/
import java.io.*;

public class Aufg0734
{
  public static void main(String args[])
  throws IOException
  {
    BufferedReader din = new BufferedReader(
		                 new InputStreamReader(System.in));
	GameOfLife game = null;

	System.out.println("Game Of Life - Text Version");
	System.out.println("(C) 1997 Guido Krueger");
	System.out.println("===========================");
	System.out.println();
	while (true) {
	  System.out.print(
        "(N)eu (G)eneration (Z)ehn (D)auer (S)et (R)eset (M)uster (E)nde -> "
      );
	  String cmd = din.readLine().toUpperCase();
	  if (cmd.equals("E")) {
		break;
	  } else if (cmd.equals("N")) {
		System.out.print("Anzahl der Zeilen: ");
		int rows = Integer.parseInt(din.readLine());
		System.out.print("Anzahl der Spalten: ");
		int cols = Integer.parseInt(din.readLine());
		game = new GameOfLife(rows, cols);
		game.printBoard();
	  } else if (cmd.equals("G")) {
		if (game != null) {
		  game.nextGeneration();
		  game.printBoard();
		}
	  } else if (cmd.equals("Z")) {
		if (game != null) {
		  for (int i = 0; i < 10; ++i) {
			game.nextGeneration();
			game.printBoard();
			try {
			  Thread.sleep(100);
			} catch (InterruptedException e) {
			  //nichts
			}
		  }
		}
	  } else if (cmd.equals("D")) {
		if (game != null) {
		  while (true) {
			game.nextGeneration();
			game.printBoard();
			try {
			  Thread.sleep(100);
			} catch (InterruptedException e) {
			  //nichts
			}
		  }
		}
	  } else if (cmd.equals("S") || cmd.equals("R")) {
		if (game != null) {
		  System.out.print("Zeile: ");
		  int row = Integer.parseInt(din.readLine());
		  System.out.print("Spalte: ");
		  int col = Integer.parseInt(din.readLine());
		  game.setCell(row, col, cmd.equals("S"));
		  game.printBoard();
		}
	  } else if (cmd.equals("M")) {
		System.out.println("1. Block         8. Warnlicht");
		System.out.println("2. Blinker       9. Pentadekathlon");
		System.out.println("3. Gleiter      10. Kröte");
		System.out.println("4. Bienenkorb   11. Uhr");
		System.out.println("5. Lastkahn     12. r-Pentomino");
		System.out.println("6. Schlange     13. Eiche");
		System.out.println("7. Schlucker");
		System.out.print("Ihre Wahl (0=Weiter): ");
		int choice = Integer.parseInt(din.readLine());
		if (choice >= 1 && choice <= 13) {
		  LifePattern pattern = new LifePattern();
		  switch (choice) {
		  case 1 : //Block
			pattern.addElement("XX");
			pattern.addElement("XX");
			break;
		  case 2 : //Blinker
			pattern.addElement("XXX");
			break;
		  case 3 : //Gleiter
			pattern.addElement("XXX");
			pattern.addElement("  X");
			pattern.addElement(" X");
			break;
		  case 4 : //Bienenkorb
			pattern.addElement(" X ");
			pattern.addElement("X X");
			pattern.addElement("X X");
			pattern.addElement(" X ");
			break;
		  case 5 : //Lastkahn
			pattern.addElement(" X  ");
			pattern.addElement("X X ");
			pattern.addElement(" X X");
			pattern.addElement("  X ");
			break;
		  case 6 : //Schlange
			pattern.addElement("X XX");
			pattern.addElement("XX X");
			break;
		  case 7 : //Schlucker
			pattern.addElement("  XX");
			pattern.addElement("XXX ");
			pattern.addElement("X   ");
			break;
		  case 8 : //Warnlicht
			pattern.addElement("  XX");
			pattern.addElement("  XX");
			pattern.addElement("XX  ");
			pattern.addElement("XX  ");
			break;
		  case 9 : //Pentadekathlon
			pattern.addElement("  X    X  ");
			pattern.addElement("XX XXXX XX");
			pattern.addElement("  X    X  ");
			break;
		  case 10 : //Kröte
			pattern.addElement(" XXX");
			pattern.addElement("XXX ");
			break;
		  case 11 : //Uhr
			pattern.addElement(" X");
			pattern.addElement("  XX");
			pattern.addElement("XX  ");
			pattern.addElement("  X ");
			break;
		  case 12 : //r-Pentomino
			pattern.addElement(" XX");
			pattern.addElement("XX ");
			pattern.addElement(" X");
			break;
		  case 13 : //Eiche
			pattern.addElement(" X");
			pattern.addElement("   X");
			pattern.addElement("XX  XXX");
			break;
		  }
		  game = new GameOfLife(22, 50);
		  game.setPattern(pattern, 12, 18);
		  game.printBoard();
		}
	  }
	}
  }
}

class GameOfLife
{
  //Konstanten
  final int neighbours[][] = {
	{-1,-1}, { 0,-1}, { 1,-1},
	{-1, 0},          { 1, 0},
	{-1, 1}, { 0, 1}, { 1, 1}
  };

  //Instanzmerkmale
  boolean board[][];
  boolean mirror[][];
  int MAXROWS;
  int MAXCOLS;

  /**
   * Der Konstruktor erzeugt die Original- und Spiegelversion des 
   * Spielplans und löscht alle Zellen auf dem Originalplan.
   */
  public GameOfLife(int rows, int cols)
  {
	MAXROWS = rows;
	MAXCOLS = cols;
	board = new boolean[rows][cols];
	mirror = new boolean[rows][cols];
	//Brett löschen
	for (int i = 0; i < MAXROWS; ++i) {
	  for (int j = 0; j < MAXCOLS; ++j) {
		board[i][j] = false;
	  }
	}
  }

  /**
   * Schaltet den Zustand einer Zelle auf lebend oder tot.
   */
  public void setCell(int row, int col, boolean state)
  {
	if (row >= 0 && row < MAXROWS && col >= 0 && col < MAXCOLS) {
	  board[row][col] = state;
	}
  }

  /**
   * Berechnet die nächste Generation des Spielplans. Dabei wurd
   * zunächst der aktuelle Zustand in ein Mirror-Array gespiegelt 
   * und dieses dann Zelle für Zelle ausgewertet. Mit Hilfe des
   * Nachbarschaftsarrays kann die Position der maximal 8 Nachbarn
   * leicht ermittelt werden. Der gespiegelte Spielplan wird nur in
   * dieser Methode verwendet, er spielt sonst keine Rolle.
   */
  public void nextGeneration()
  {
	//Brett spiegeln
	for (int i = 0; i < MAXROWS; ++i) {
	  for (int j = 0; j < MAXCOLS; ++j) {
		mirror[i][j] = board[i][j];
	  }
	}
	//Nachfolgegeneration berechnen
	for (int i = 0; i < MAXROWS; ++i) {
	  for (int j = 0; j < MAXCOLS; ++j) {
		//Anzahl der Nachbarn bestimmen
		int neighbourcnt = 0;
		for (int k = 0; k < 8; ++k) {
		  int ni = i + neighbours[k][0];
		  int nj = j + neighbours[k][1];
		  if (ni >= 0 && ni < MAXROWS && nj >= 0 && nj < MAXCOLS) {
			if (mirror[ni][nj]) {
			  ++neighbourcnt;
			}
		  }
		}
		//Nachfolgezustand setzen
		board[i][j] = neighbourcnt == 3 || (board[i][j] && neighbourcnt == 2);
	  }
	}
  }

  /**
   * Gibt den aktuellen Spielplan im Textmodus auf dem Bildschirm aus.
   */
  public void printBoard()
  {
	for (int i = 0; i < MAXROWS; ++i) {
	  for (int j = 0; j < MAXCOLS; ++j) {
		System.out.print(board[i][j] ? "X" : "-");
	  }
	  System.out.println();
	}
	System.out.println();
  }

  /**
   * Erlaubt es, ein LifePattern-Objekt an einer beliebigen Stelle
   * auf dem Spielplan zu positionieren und bietet damit eine einfach
   * zu bedienede pseudo-graphische Schnittstelle zur Eingabe von 
   * Mustern.
   */
  public void setPattern(LifePattern pattern, int row, int col)
  {
	int maxrows = pattern.getMaxRows();
	int maxcols = pattern.getMaxCols();
	for (int i = 0; i < maxrows; ++i) {
	  for (int j = 0; j < maxcols; ++j) {
		if (pattern.getCell(i, j)) {
		  int rpos = row + i;
		  int cpos = col + j;
		  if (rpos >= 0 && rpos < MAXROWS && cpos >= 0 && cpos < MAXCOLS) {
			board[rpos][cpos] = true;
		  }
		}
	  }
	}	
  }
}

class LifePattern
{
  //Konstanten
  final int MAXROWS = 10;
  final int MAXCOLS = 10;

  //Instanzmerkmale
  boolean pattern[][];
  int currentrow;

  public LifePattern()
  {
	pattern = new boolean[MAXROWS][MAXCOLS];
	currentrow = 0;
	//Pattern löschen
	for (int i = 0; i < MAXROWS; ++i) {
	  for (int j = 0; j < MAXCOLS; ++j) {
		pattern[i][j] = false;
	  }
	}
  }

  public void addElement(String row)
  {
	if (currentrow < MAXROWS) {
	  for (int i = 0; i < row.length() && i < MAXCOLS; ++i) {
		if (row.charAt(i) != ' ') {
		  pattern[currentrow][i] = true;
		}
	  }
	  ++currentrow;
	}
  }

  public int getMaxRows()
  {
	return MAXROWS;
  }

  public int getMaxCols()
  {
	return MAXCOLS;
  }

  public boolean getCell(int row, int col)
  {
	if (row >= 0 && row < MAXROWS && col >= 0 && col < MAXCOLS) {
	  return pattern[row][col];
	}
	return false;
  }
}

