package jpp.numbergame;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
/**
* Die NumberGame-Klasse stellt eine Spielrunde von 2048 dar und beinhaltet die
* Spiellogik. Sie speichert den aktuellen Zustand des Spielbretts und fuehrt
* Zuege des Spielers darauf aus. Ausserdem verwaltet sie die Punktzahl des
* Spielers.
*
* @author Marcel
*
*/
public class NumberGame {
private Tile gameBoard[][];
private int width;
private int height;
private int gamePoints;
/**
* Erstellt ein neues Spiel mit einem Spielfeld, welches width Felder breit
* und height Felder hoch ist. Alle Felder des Spieles sind zu Beginn leer.
* Die Punktzahl des Spielers ist auf 0 zu setzen. Ist width oder height
* kleiner als 1, wird eine IllegalArgumentException geworfen.
*
* @param width
* @param height
*/
public NumberGame(int width, int height) {
if (width < 1 || height < 1) {
throw new IllegalArgumentException("Das Feld ist zu klein");
}
gameBoard = new Tile[width][height];
this.width = width;
this.height = height;
gamePoints = 0;
}
/**
* Wie oben, platziert jedoch initialTiles zufaellige Teile (siehe
* addRandomTile-Methode unten). Ist initialTiles kleiner als 0 oder
* groesser als width * height, soll eine IllegalArgumentException geworfen
* werden.
*
* @param width
* @param height
* @param initialTiles
*/
public NumberGame(int width, int height, int initialTiles) {
this(width, height);
if (initialTiles < 0 || initialTiles > width * height) {
throw new IllegalArgumentException("Fehler");
}
for (int i = 0; i < initialTiles; i++) {
this.addRandomTile();
}
}
/**
* Liefert den Wert des Teils an der als Coordinate2D oder x- und y-Wert
* angegebenen Position des Spielfeldes, wobei die x-Achse nach rechts und
* die y-Achse nach unten zeigt (Dieses Koordinatensystem wird auch von
* JavaFX benutzt, dies erleichtert spaeter die Implementation der GUI).
* Befindet sich kein Teil auf dem angegebenen Feld, wird 0 zurueckgegeben.
* Liegt die Koordinate ausserhalb des Feldes, wird eine
* IndexOutOfBoundsException geworfen.
*
* @param coord
* @return
*/
public int get(Coordinate2D coord) {
if (coord.getX() >= this.width || coord.getY() >= this.height
|| coord.getX() < 0 || coord.getY() < 0) {
throw new IndexOutOfBoundsException("Nicht auf dem Spielfeld");
}
int x = coord.getX();
int y = coord.getY();
if (gameBoard[x][y] == null) {
return 0;
}
return gameBoard[x][y].getValue();
}
/**
* Liefert den Wert des Teils an der als Coordinate2D oder x- und y-Wert
* angegebenen Position des Spielfeldes, wobei die x-Achse nach rechts und
* die y-Achse nach unten zeigt (Dieses Koordinatensystem wird auch von
* JavaFX benutzt, dies erleichtert spaeter die Implementation der GUI).
* Befindet sich kein Teil auf dem angegebenen Feld, wird 0 zurueckgegeben.
* Liegt die Koordinate ausserhalb des Feldes, wird eine
* IndexOutOfBoundsException geworfen.
*
* @param x
* @param y
* @return
*/
public int get(int x, int y) {
if (gameBoard[x][y] == null) {
return 0;
}
return gameBoard[x][y].getValue();
}
/**
* Gibt die aktuelle Punktzahl des Spielers zurueck.
*
* @return
*/
public int getPoints() {
return gamePoints;
}
/**
* Gibt true zurueck, wenn alle Felder des Spielbretts mit einem Teil belegt
* sind.
*
* @return true
*/
public boolean isFull() {
for (int i = 0; i < gameBoard.length; i++) {
for (int e = 0; e < gameBoard[i].length; e++) {
if (gameBoard[e][i] == null) {
return false;
}
}
}
return true;
}
/**
* Fuegt ein neues Teil an einer zufaelligen freien Position des Spielfelds
* ein (benutzen Sie java.util.Random). Der Wert des neuen Teils wird mit
* einer Chance von ca. 10% auf 4 gesetzt, ansonsten auf 2.
*
* @return
*/
public Tile addRandomTile() {
Random rnd = new Random();
int posX = rnd.nextInt(this.width);
int posY = rnd.nextInt(this.height);
int newValue = 0;
float chance = rnd.nextFloat();
if (chance <= 0.10f) {
newValue = 4;
} else {
newValue = 2;
}
while(get(posX,posY) != 0)
{
posX = rnd.nextInt(this.width);
posY = rnd.nextInt(this.height);
}
gameBoard[posX][posY] = new Tile(new Coordinate2D(posX, posY), newValue);
return new Tile(new Coordinate2D(posX, posY), newValue);
}
/**
* Fuegt ein neues Teil an der angegebenen Position des Spielfelds mit dem
* Wert value ein. Ist das angegebene Feld bereits belegt, wird eine
* TileExistsException geworfen.
*
* @param x
* X-Koordinate
* @param y
* Y-Koordinate
* @param value
* Wert der Position
* @return field
*/
public Tile addTile(int x, int y, int value) {
int fieldValue = get(x, y);
if (fieldValue != 0) {
throw new TileExistsException("Feld bereits belegt!");
}
Tile field = new Tile(new Coordinate2D(x,y), value) ;
gameBoard[x][y] = field ;
return field;
}
/**
* Fuehrt einen Spielerzug aus. Alle Teile werden entsprechend der oben
* beschriebenen Regeln in die uebergebene Richtung bewegt.
*
* @param dir
* @return
*/
public List<Move> move(Direction dir) {
int xB = 0;
LinkedList<Move> list = new LinkedList<Move>();
if (dir == Direction.UP) {
for (int t = 0; t < 2; t++) {
for (int i = 0; i < gameBoard.length; i++) {
for (int e = 0; e < gameBoard[i].length; e++) {
if (gameBoard[e][i] == null) {
for (int r = e; r < gameBoard.length; r++) {
if (gameBoard[r][i] != null) {
gameBoard[e][i] = new Tile(
new Coordinate2D(e, i), get(r, i));
list.add(new Move(new Coordinate2D(e + xB,
i), new Coordinate2D(e, i), get(r,
i), get(r, i)));
gameBoard[r][i] = null;
xB = r - e;
break;
}
}
}
if (e > 0 && t == 0) {
if (get(e - 1, i) == get(e, i) && get(e, i) > 0) {
list.add(new Move(new Coordinate2D(e + xB, i),
new Coordinate2D(e - 1, i), get(e - 1,
i), get(e - 1, i) * 2));
gamePoints += get(e - 1, i) * 2;
gameBoard[e - 1][i] = new Tile(
new Coordinate2D(e - 1, i), get(e - 1,
i) * 2);
gameBoard[e][i] = null;
}
}
}
}
}
} else if (dir == Direction.LEFT) {
for (int t = 0; t < 2; t++) {
for (int i = 0; i < gameBoard.length; i++) {
for (int e = 0; e < gameBoard[i].length; e++) {
if (gameBoard[i][e] == null) {
for (int r = e; r < gameBoard.length; r++) {
if (gameBoard[i][r] != null) {
gameBoard[i][e] = new Tile(
new Coordinate2D(i, e), get(i, r));
System.out.print(get(i, r));
list.add(new Move(new Coordinate2D(i, e
+ xB), new Coordinate2D(i, e), get(
i, r), get(i, r)));
gameBoard[i][r] = null;
xB = r - e;
break;
}
}
}
if (e > 0 && t == 0) {
if (get(i, e - 1) > 0 && get(i, e - 1) == get(i, e)) {
list.add(new Move(new Coordinate2D(i, e),
new Coordinate2D(i, e - 1), get(i, e),
get(i, e - 1) * 2));
gamePoints += get(i, e - 1) * 2;
gameBoard[i][e - 1] = new Tile(
new Coordinate2D(i, e - 1), get(i,
e - 1) * 2);
gameBoard[i][e] = null;
}
}
}
}
}
} else if (dir == Direction.DOWN) {
for (int t = 0; t < 2; t++) {
for (int i = gameBoard.length - 1; i >= 0; i--) {
for (int e = gameBoard[i].length - 1; e >= 0; e--) {
if (gameBoard[e][i] == null) {
for (int r = e; r >= 0; r--) {
if (gameBoard[r][i] != null) {
gameBoard[e][i] = new Tile(
new Coordinate2D(e, i), get(r, i));
list.add(new Move(new Coordinate2D(e, i),
new Coordinate2D(e + 1, i), get(r,
i), get(r, i)));
gameBoard[r][i] = null;
xB = r - e;
break;
}
}
}
if (e < gameBoard.length - 1 && t == 0) {
if (get(e + 1, i) == get(e, i) && get(e, i) > 0) {
list.add(new Move(new Coordinate2D(e - xB, i),
new Coordinate2D(e + 1, i), get(e + 1,
i), get(e + 1, i) * 2));
gamePoints += get(e + 1, i) * 2;
gameBoard[e + 1][i] = new Tile(
new Coordinate2D(e + 1, i), get(e + 1,
i) * 2);
gameBoard[e][i] = null;
}
}
}
}
}
} else if (dir == Direction.RIGHT) {
for (int t = 0; t < 2; t++) {
for (int i = gameBoard.length - 1; i >= 0; i--) {
for (int e = gameBoard[i].length - 1; e >= 0; e--) {
if (gameBoard[i][e] == null) {
for (int r = e; r >= 0; r--) {
if (gameBoard[i][r] != null) {
gameBoard[i][e] = new Tile(
new Coordinate2D(i, e), get(i, r));
list.add(new Move(new Coordinate2D(i, e),
new Coordinate2D(i, e + 1), get(i,
r), get(i, r)));
gameBoard[i][r] = null;
xB = r - e;
break;
}
}
}
if (e < gameBoard.length - 1 && t == 0) {
if (get(i, e + 1) > 0 && get(i, e + 1) == get(i, e)) {
list.add(new Move(new Coordinate2D(i, e - xB),
new Coordinate2D(i, e + 1), get(i, e),
get(i, e + 1) * 2));
gamePoints += get(i, e + 1) * 2;
gameBoard[i][e + 1] = new Tile(
new Coordinate2D(i, e + 1), get(i,
e + 1) * 2);
gameBoard[i][e] = null;
}
}
}
}
}
}
return list;
}
/**
* Gibt true zurueck, wenn eine Bewegung in die angegebene Richtung moeglich
* ist, d.h. es wuerde mindestens ein Teil dadurch bewegt.
*
* @param dir
* @return true
*/
public boolean canMove(Direction dir) {
if (dir == Direction.UP) {
for (int i = 0; i < gameBoard.length; i++) {
for (int e = 1; e < gameBoard[i].length; e++) {
if (gameBoard[e][i] != null && gameBoard[e - 1][i] == null) {
return true;
}
}
}
} else if (dir == Direction.RIGHT) {
for (int i = gameBoard.length - 1; i >= 0; i--) {
for (int e = gameBoard[i].length - 2; e >= 0; e--) {
if (gameBoard[i][e] != null && gameBoard[i][e + 1] == null) {
return true;
}
}
}
} else if (dir == Direction.DOWN) {
for (int i = gameBoard.length - 1; i >= 0; i--) {
for (int e = gameBoard[i].length - 2; e >= 0; e--) {
if (gameBoard[e][i] != null && gameBoard[e + 1][i] == null) {
return true;
}
}
}
}
else if (dir == Direction.LEFT) {
for (int i = 0; i < gameBoard.length; i++) {
for (int e = 1; e < gameBoard[i].length; e++) {
if (gameBoard[i][e] != null && gameBoard[i][e - 1] == null) {
return true;
}
}
}
}
return false;
}
/**
* Gibt true zurueck, wenn eine Bewegung in mindestens eine der vier
* Richtungen moeglich ist. Anderenfalls hat der Spieler verloren.
*
* @return true
*/
public boolean canMove() {
if (canMove(Direction.DOWN)) {
return true;
}
if (canMove(Direction.UP)) {
return true;
}
if (canMove(Direction.LEFT)) {
return true;
}
if (canMove(Direction.RIGHT)) {
return true;
}
return false;
}
public void printField() {
for (int i = 0; i < gameBoard.length; i++) {
for (int e = 0; e < gameBoard[i].length; e++) {
if (gameBoard[i][e] == null) {
System.out.print("0 ");
} else {
System.out.print(gameBoard[i][e].getValue() + " ");
}
}
System.out.println();
}
}
}