452 lines
9.2 KiB
Java
452 lines
9.2 KiB
Java
package ch.bfh.sevennotseven;
|
|
|
|
import java.awt.Point;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Random;
|
|
|
|
public class Game {
|
|
|
|
interface UpdateListener {
|
|
public void gameUpdate();
|
|
}
|
|
|
|
|
|
// Constants
|
|
static final int numberOfColors = 5;
|
|
static final int linesPerLevel = 40;
|
|
|
|
// Private members
|
|
private int[][] field;
|
|
private ArrayList<Integer> nextBlocks;
|
|
private int level;
|
|
private int score;
|
|
private int size;
|
|
private int freeBlocks;
|
|
private int freeMoves;
|
|
private int numUndos;
|
|
private int linesLeft;
|
|
private Random rand;
|
|
private ArrayList<UpdateListener> updateListeners;
|
|
|
|
public Game(){
|
|
this(7);
|
|
}
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @author aaron
|
|
* @param size
|
|
*/
|
|
public Game (int size) {
|
|
rand = new Random(); // Initialize random object
|
|
this.reset(size);
|
|
}
|
|
|
|
public void addUpdateListener(UpdateListener listener) {
|
|
updateListeners.add(listener);
|
|
}
|
|
public void removeUpdateListener(UpdateListener listener) {
|
|
updateListeners.remove(listener);
|
|
}
|
|
private void emitUpdateEvent() {
|
|
for(UpdateListener e: updateListeners) {
|
|
e.gameUpdate();
|
|
}
|
|
}
|
|
|
|
public boolean isGameOver(){
|
|
return false;
|
|
|
|
}
|
|
|
|
public int getSize() {
|
|
return size;
|
|
}
|
|
|
|
public int getScore(){
|
|
return score;
|
|
|
|
}
|
|
|
|
public int getLinesLeft() {
|
|
return linesLeft;
|
|
}
|
|
|
|
public int getLevel(){
|
|
return level;
|
|
}
|
|
|
|
public List<Integer> getNextBlocks(){
|
|
return nextBlocks;
|
|
}
|
|
|
|
public int[][] getField(){
|
|
return field;
|
|
|
|
}
|
|
|
|
public boolean canMove(Point src, Point dst){
|
|
return getPath(src, dst)!=null;
|
|
|
|
}
|
|
|
|
/**
|
|
* Check if there is a valid path from src to dst and move a block if possible.
|
|
*
|
|
* @author aaron
|
|
* @param src
|
|
* @param dst
|
|
* @return
|
|
*/
|
|
public boolean doMove(Point src, Point dst){
|
|
if(field[src.x][src.y]==0 ||field[dst.x][dst.y] !=0 || src.equals(dst)) {
|
|
return false;
|
|
}
|
|
|
|
if(!canMove(src, dst)) {
|
|
return false; // checking if there is a path from src to dest
|
|
}
|
|
|
|
field[dst.x][dst.y] = field[src.x][src.y];
|
|
field[src.x][src.y] = 0;
|
|
|
|
nextStep(dst); // cleanup rows or add new blocks
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Pathfinding of shortest path between src and dst.
|
|
*
|
|
* @author aaron
|
|
* @param src
|
|
* @param dst
|
|
* @return
|
|
*/
|
|
public List<Point> getPath(final Point src, final Point dst){
|
|
|
|
ArrayList<Vertex> vertices = new ArrayList<Vertex>(); // List of vertices
|
|
|
|
vertices.add(new Vertex(0, src));
|
|
|
|
// Get a verticies list from the field data
|
|
for(int i= 0; i < size; i++){
|
|
for(int j = 0; j < size; j++){
|
|
if(field[i][j] == 0 && (src.x!=i || src.y!=j)){ //field empty and not src
|
|
vertices.add(new Vertex(Integer.MAX_VALUE, new Point(i, j)));
|
|
}
|
|
}
|
|
}
|
|
|
|
ArrayList<Vertex> allVerticies = new ArrayList<Vertex>(vertices); // List of vertices
|
|
|
|
while(!vertices.isEmpty()){ // As long as there are vertices
|
|
final Vertex u = findNearestVertex(vertices);
|
|
vertices.remove(u); // Remove u from the set of vertices
|
|
|
|
final Point[] offsets = {
|
|
new Point(0,1),
|
|
new Point(0,-1),
|
|
new Point(1,0),
|
|
new Point(-1,0)
|
|
};
|
|
|
|
for(int i = 0; i < 4; i++){ // for each neighbour of u ...
|
|
final Point p = u.getPos();
|
|
final Point offs = offsets[i];
|
|
int x = p.x + offs.x;
|
|
int y = p.y + offs.y;
|
|
if(x<0 || y<0 || x>=size || y>= size) continue;
|
|
|
|
final Vertex v = findVertex(x,y, vertices);
|
|
//distanz_update(u,v)
|
|
if(v!=null){
|
|
int alternative = u.getDist()+1;
|
|
if( alternative< v.getDist()) {
|
|
v.setDist(alternative);
|
|
v.setPrev(u);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return reconstructShortestPath(allVerticies, src,dst);
|
|
}
|
|
|
|
public boolean doUndo(){
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Do a free move if freeMoves < 0.
|
|
*
|
|
* @author aaron
|
|
* @param src
|
|
* @param dst
|
|
* @return
|
|
*/
|
|
public boolean doFreeMove(Point src, Point dst){
|
|
//move without path checking
|
|
if(getAvailFreeMoves() <= 0 ) {
|
|
return false;
|
|
}
|
|
|
|
if(field[src.x][src.y]==0) {
|
|
return false;
|
|
}
|
|
|
|
field[dst.x][dst.y] = field[src.x][src.y];
|
|
field[src.x][src.y] = 0;
|
|
|
|
freeMoves--;
|
|
|
|
nextStep(dst); //cleanup rows or add new blocks
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
public int getAvailFreeMoves(){
|
|
return freeMoves;
|
|
}
|
|
|
|
public int getAvailUndo(){
|
|
return numUndos;
|
|
}
|
|
|
|
/**
|
|
* Reset game score, field and state.
|
|
*
|
|
* @author aaron
|
|
*/
|
|
public void reset(int size){
|
|
this.size = size;
|
|
this.freeBlocks = size * size;
|
|
this.updateListeners = new ArrayList<UpdateListener>();
|
|
|
|
// Initialize new blocks
|
|
nextBlocks = new ArrayList<Integer>();
|
|
nextBlocks.add(1);
|
|
nextBlocks.add(2);
|
|
nextBlocks.add(3);
|
|
|
|
// Initialize field, level and score
|
|
field = new int[size][size];
|
|
level = 1;
|
|
score = 0;
|
|
numUndos = 0;
|
|
freeMoves = 0;
|
|
linesLeft=linesPerLevel;
|
|
|
|
// Populate game field
|
|
this.populateField();
|
|
}
|
|
|
|
/**
|
|
* Finds the nearest vertex to start
|
|
*
|
|
* @author aaron
|
|
* @param vertices
|
|
* @return
|
|
*/
|
|
private Vertex findNearestVertex(final List<Vertex> vertices){
|
|
Vertex tmp = vertices.get(0);
|
|
|
|
for (int i = 1; i < vertices.size(); i++) {
|
|
Vertex n = vertices.get(i);
|
|
if(n.getDist() < tmp.getDist()) {
|
|
tmp = n;
|
|
|
|
}
|
|
}
|
|
|
|
return tmp;
|
|
}
|
|
|
|
/**
|
|
* Helper function for pathfinding. Finds a vertex corresponding to the given coordinate.
|
|
*
|
|
* @author aaron
|
|
* @param x
|
|
* @param y
|
|
* @param vertices
|
|
* @return
|
|
*/
|
|
private Vertex findVertex(int x, int y, final List<Vertex> vertices) {
|
|
return findVertex(new Point(x,y), vertices);
|
|
|
|
}
|
|
|
|
/**
|
|
* Helper function for pathfinding. Finds a vertex corresponding to the given point.
|
|
*
|
|
* @author aaron
|
|
* @param pos
|
|
* @param vertices
|
|
* @return
|
|
*/
|
|
private Vertex findVertex(final Point pos, final List<Vertex> vertices) {
|
|
for (int i = 0; i < vertices.size(); i++) {
|
|
Vertex n = vertices.get(i);
|
|
if(n.getPos().equals(pos)) {
|
|
return n;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Helper function for pathfinding. Returns shortest path between src and dst in a given set of vertices.
|
|
*
|
|
* @author aaron
|
|
* @param vertices
|
|
* @param src
|
|
* @param dst
|
|
* @return
|
|
*/
|
|
private List<Point> reconstructShortestPath(final List<Vertex> vertices, final Point src, final Point dst) {
|
|
ArrayList<Point> path = new ArrayList<Point>();
|
|
path.add(dst);
|
|
Vertex u = findVertex(dst, vertices);
|
|
if(u==null) {
|
|
return null;
|
|
}
|
|
while(u.getPrev()!=null) {
|
|
u= u.getPrev();
|
|
path.add(0, u.getPos());
|
|
}
|
|
if(u!=findVertex(src, vertices)) {
|
|
return null;
|
|
}
|
|
return path;
|
|
}
|
|
/**
|
|
* Calculates the next game step. This method will either call populateField, or it will cleanup blocks
|
|
*
|
|
* @author aaron
|
|
* @param lastPoint
|
|
*/
|
|
private void nextStep(final Point lastPoint) {
|
|
if(!checkRemoveBlocks(lastPoint)){
|
|
populateField(); //add new blocks
|
|
} else {
|
|
linesLeft--;
|
|
if(linesLeft==0) {
|
|
level++;
|
|
linesLeft=linesPerLevel;
|
|
|
|
}
|
|
}
|
|
emitUpdateEvent();
|
|
}
|
|
|
|
/**
|
|
* Collision detection and block removal if there are 4 or more blocks in a row in any direction.
|
|
*
|
|
* @author aaron
|
|
* @param lastPoint
|
|
*/
|
|
private boolean checkRemoveBlocks(final Point lastPoint){
|
|
|
|
final Point[] offsets = {
|
|
new Point(0,1), // right
|
|
new Point(0,-1), // left
|
|
new Point(1,0), // bottom
|
|
new Point(-1,0), // top
|
|
new Point(1,1), // bottom right
|
|
new Point(-1,-1),// top left
|
|
new Point(-1,1), // bottom left
|
|
new Point(1,-1) // top right
|
|
};
|
|
|
|
int matches[] = new int[8];
|
|
int color = field[lastPoint.x][lastPoint.y];
|
|
|
|
for(int i = 0; i < 8; i++){
|
|
Point offset = offsets[i];
|
|
Point current = new Point(lastPoint);
|
|
|
|
int matchcount = 0;
|
|
|
|
|
|
while(true){
|
|
current.translate(offset.x, offset.y);
|
|
if(current.x < 0 || current.x >= size || current.y < 0 || current.y >= size) break;
|
|
if(field[current.x][current.y] != color) break;
|
|
matchcount++;
|
|
}
|
|
|
|
matches[i] = matchcount;
|
|
}
|
|
|
|
int distinctmatches = 0;
|
|
|
|
for(int i = 0; i < 4; i++){
|
|
int totalmatches = 1 + matches[i*2] + matches[i*2+1];
|
|
if(totalmatches >= 4){
|
|
distinctmatches++;
|
|
for(int j = 0; j < 2; j++){
|
|
Point offset = offsets[j+i*2];
|
|
Point current = new Point(lastPoint);
|
|
|
|
for(int k = 0; k < matches[j+i*2]; k++){
|
|
current.translate(offset.x, offset.y);
|
|
field[current.x][current.y] = 0;
|
|
freeBlocks++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(distinctmatches > 0){
|
|
field[lastPoint.x][lastPoint.y] = 0;
|
|
freeBlocks++;
|
|
|
|
if(distinctmatches > 1){
|
|
freeMoves++;
|
|
}
|
|
|
|
int sum = 0;
|
|
|
|
for( Integer i : matches ) sum += i;
|
|
|
|
score += (1 + distinctmatches * sum);
|
|
|
|
System.out.println("Score: " + score);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
/**
|
|
* Adds n new blocks to random positions on the field,
|
|
* according to the level number.
|
|
*/
|
|
private void populateField(){
|
|
|
|
// while there are blocks left in nextBlocks
|
|
while((nextBlocks.size() > 0) && (freeBlocks > 0)){
|
|
int x = rand.nextInt(size); // get random x position
|
|
int y = rand.nextInt(size); // get random y position
|
|
|
|
// if the position is free
|
|
if(field[x][y] == 0){
|
|
field[x][y] = nextBlocks.remove(0); // fill with the first element of nextBlocks
|
|
freeBlocks--;
|
|
checkRemoveBlocks(new Point(x,y));
|
|
}
|
|
}
|
|
|
|
// add n new colors to nextBlocks according to the level number.
|
|
for(int i = 0; i < (level * 3); i++){
|
|
nextBlocks.add(1 + rand.nextInt(numberOfColors));
|
|
}
|
|
}
|
|
}
|