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 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 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 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 getPath(final Point src, final Point dst){ ArrayList vertices = new ArrayList(); // 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 allVerticies = new ArrayList(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(); // Initialize new blocks nextBlocks = new ArrayList(); 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 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 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 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 reconstructShortestPath(final List vertices, final Point src, final Point dst) { ArrayList path = new ArrayList(); 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)); } } }