GitList
Repositories
Help
Report an Issue
puyopuyo
Code
Commits
Branches
Tags
Search
Tree:
2419458
Branches
Tags
master
puyopuyo
backEnd.java
adding puyopuyo code.
Dev
commited
2419458
at 2018-07-21 10:16:11
backEnd.java
Blame
History
Raw
/* * backEnd.java * * Created on 31 March, 2007, 11:42 PM * * To change this template, choose Tools | Template Manager * and open the template in the editor. */ import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.Stack; import java.util.Vector; /** * Holds all the logic that drives the game. * <p>The main engine of the game is comprised of the following * four methods: * <ul> * <li>{@link puyopuyo.backEnd#popPuyos(puyo[]) popPuyos} - This acts as an entry point to {@link puyopuyo.frontEnd# frontEnd} to the logic of the game.</li> * <li>{@link puyopuyo.backEnd#probe(puyo) probe(puyo)} - It identifies the chain due to puyo passed to it as a parameter.</li> * <li>{@link puyopuyo.backEnd#popAndMarkDroppings(HashSet) popAndMarkDroppings} - It removes the puyo from {@link puyopuyo.backEnd#boardMap boardMap}</li> * and also marks the puyo which is to move down because of popping.</li> * <li>{@link puyopuyo.backEnd#settleDown(puyo) settleDown} - Moves down the puyo (and all those above it) to fill</li> * in the void created due to popping of puyo.</li> * </ul> * </p> * @author Dev Ghai */ public class backEnd extends puyo{ /** * Creates a new instance of backEnd, initialises the board * and pushes a pair of puyos in {@link puyopuyo.backEnd#boardMap boardMap} using {@link puyopuyo.backEnd#pushPuyos() pushPuyos}. * {@link puyopuyo.backEnd#boardMap boardMap} is a two dimensional array that the heart of the game. It logically * represents the game's board in memory in form of an array whose width * is equal to {@link puyopuyo.backEnd#puyosOnXAxis puyosOnXAxis} and height is equal to {@link puyopuyo.backEnd#puyosOnYAxis puyosOnYAxis}. */ public backEnd() { for (int i = 0; i<puyosOnXAxis; i++) for(int j = 0; j<puyosOnYAxis; j++) boardMap[i][j] = null; //=> board is initially empty pushPuyos(); //and let the game begin! } //Clean and Build the Project to see the following 2 values take effect. /** * Holds the number of puyos on X Axis. Modify this value to effectively * change the size of the board. Logic has been made to depend on it. */ int puyosOnXAxis = 6; /** * Holds the number of puyos on Y Axis. Modify this value to effectively * change the size of the board. Logic has been made to depend on it. */ int puyosOnYAxis = 12; /** * Holds the minimum length of the chain in terms of puyos that will cause puyos * to be popped from the board. */ int popmin = 4; /** * Stores the pair of puyos pushed onto the board. */ private puyo puyoPair [] = new puyo[2]; /** * Returns a pair of puyos in form of an one dimensional array. */ public puyo[] getPuyoPair(){ return puyoPair; } /** * Integer value that holds the score of the game. Popping of one puyo * adds 10 to the current score. */ private int score = 0; /** * Returns the current score. {@link puyopuyo.backEnd#score score} is incremented by 10 points when * one puyo is popped. */ public int getScore(){ return score; } /** * A boolean value that tells whether the game is over or not. * * <p>This value is set in two cases - * <li> * <ol>When {@link puyopuyo.backEnd#pushPuyos() pushPuyos} is unable to push a pair on the board.</ol> * <ol>When the user hits the Esc key, i.e. s/he wants to exit from the game.</ol> * </li></p> */ private boolean gameOver = false; /** * Logically tells if the game is over or not by returning the (boolean) value * of {@link puyopuyo.backEnd#gameOver gameOver}. */ public boolean isGameOver(){ return gameOver; } /** * This method allows {@link puyopuyo.frontEnd# frontEnd} to set the gameOver to true. * <p>Game ends in two cases - * <ol><li>When {@link puyopuyo.backEnd#pushPuyos() pushPuyos} is unable to push a pair on the board.</li> * <li>When the user hits the Esc key, i.e. s/he wants to exit from the game.</li> * </ol></p> * @param status 'status' of the game to be copied to {@link gameOver}. */ public void setGameOver(boolean status){ gameOver = status; } /** * This two dimensional array is the heart of the game. It logically * represents the game's board in memory in form of an array whose width * is equal to {@link puyopuyo.backEnd#puyosOnXAxis puyosOnXAxis} and height is equal to {@link puyopuyo.backEnd#puyosOnYAxis puyosOnYAxis}. */ private puyo boardMap[][] = new puyo[puyosOnXAxis][puyosOnYAxis]; /** * Returns true if insertion successfully takes place in {@link puyopuyo.backEnd#boardMap boardMap}. * @param p Puyo to insert in boardMap * @param X X Co-ordinate of the insertion point. * @param Y Y co-ordinate of the insertion point. * @return Returns if the insertion was successful or not. */ private synchronized boolean insertInBoardMap(puyo p, int X, int Y){ /*If boardMap[X,Y] holds null => it is empty & the new point obeys * boundary conditions. Set the same and return true, * else that cell is filled or lies outside board, return false.*/ boolean newPointLiesOnBoard = (X>=0 & X<puyosOnXAxis) & (Y>=0 & Y<puyosOnYAxis); if(newPointLiesOnBoard && boardMap[X][Y]==null){ boardMap[X][Y] = p; return true; } //control will reach here only when the cell is previously filled or the point lies outside the board return false; } /** * Removes puyo from the {@link puyopuyo.backEnd#boardMap boardMap}. * @param X X co-ordinate in boardMap from which to remove puyo. * @param Y Y co-ordinate of boardMap from which to remove the puyo. */ private synchronized void removeFromBoardMap(int X, int Y){ boolean pointLiesOnBoard = (X>=0 & X<puyosOnXAxis) & (Y>=0 & Y<puyosOnYAxis); if (pointLiesOnBoard && boardMap[X][Y]!=null) boardMap[X][Y] = null; } /** * Returns {@link puyopuyo.backEnd#boardMap boardMap}. One of the main purposes of * this method is to provide a two dimensional array to {@link puyopuyo.frontEnd# frontEnd} * for printing the board onto screen. {@link puyopuyo.frontEnd#drawBoardMap() drawBoardMap} * uses this method to extract the logical {@link puyopuyo.backEnd#boardMap boardMap} and print * it onto the screen. * @return boardMap */ public puyo[][] getBoardMap(){ return boardMap; } /** * Generates a new {@link puyo# puyo} and randomly assigns color to the generated {@link puyo# puyo}. * @return A puyo with randomly generated colors. */ private puyo getNewPuyo(){ Math.random(); //initiate the random number generator by first call puyo p = new puyo(); //initialize p switch((int)(Math.random()*10)%4) { case 0: p = new puyo("./images/redPuyo.png", 'r'); //red puyo break; case 1: p = new puyo("./images/greenPuyo.png", 'g'); //green puyo break; case 2: p = new puyo("./images/bluePuyo.png", 'b'); //blue puyo break; case 3: p = new puyo("./images/yellowPuyo.png", 'y'); //yellow puyo } return p; } /** * Pushes a new pair of puyos in {@link puyopuyo.backEnd#boardMap boardMap} at the top of * the board. If no new pair can be pushed, {@link puyopuyo.backEnd#gameOver gameOver} is set true via {@link puyopuyo.backEnd#setGameOver(boolean) setGameOver}. */ public void pushPuyos(){ int centerX = (puyosOnXAxis-1)/2; if(boardMap[centerX][0]==null & boardMap[centerX+1][0]==null) { puyo p1 = getNewPuyo(); puyo p2 = getNewPuyo(); p1.setX(centerX); p1.setY(0); boardMap[centerX][0] = p1; p2.setX(++centerX); p2.setY(0); //centerX+1 causes second puyo to be printed over first puyo //Hence v c only 1 puyo when a pair is pushed on board. boardMap[centerX][0] = p2; puyoPair[0] = p1; puyoPair[1] = p2; } else setGameOver(true); //!! } /** * Moves the puyo passed as parameter one positon to left in {@link puyopuyo.backEnd#boardMap boardMap}. * It also updates the property of the puyo after successfully moving it. * @param p puyo to be moved left. * @return True if puyo was successfully moved and its property updated, else false */ public boolean moveLeft(puyo p){ boolean retVal; if( retVal = insertInBoardMap(p, p.getX() - 1, p.getY()) ){ removeFromBoardMap(p.getX(), p.getY()); p.setX(p.getX()-1); //update the puyo's property } return retVal; } /** * Moves the puyo passed as parameter one positon to right in {@link puyopuyo.backEnd#boardMap boardMap}. * It also updates the property of the puyo after successfully moving it. * @return True if puyo was successfully moved and its property updated, else false * @param p Puyo to be moved right. */ public boolean moveRight(puyo p){ boolean retVal; if( retVal = insertInBoardMap(p, p.getX() + 1, p.getY())){ removeFromBoardMap(p.getX(), p.getY()); p.setX(p.getX()+1); //update puyo's property } return retVal; } /** * Moves the puyo passed as parameter one positon down in {@link puyopuyo.backEnd#boardMap boardMap}. * It also updates the property of the puyo after successfully moving it. * @return True if puyo was successfully moved and its property updated, else false * @param p Puyo to be moved down. */ public boolean moveDown(puyo p){ /* while(boundary conditions are ok & next cell is empty) * The loop will exit when an attempt will be made to insert an imaginary * puyo above the topmost puyo. */ if( insertInBoardMap(p, p.getX(), p.getY()+1) ){ removeFromBoardMap(p.getX(), p.getY()); p.setY(p.getY()+1); return true; } return false; } /** * Rotates clockwise the puyo p[1] around p[0]. * <p><b>DETAILED DESCRIPTION:</b><br> * p[0] will always be the pivot around which p[1] will rotate<br> * Determine the orientation of the pair: HORIZONTAL or VERTICAL<br> * Determine position of p[1] : NORTH EAST SOUTH or WEST<br> * Move according to the determined position.<br> * Simple?? * </p> * @param p Pair of puyos to rotate. */ public void rotate(puyo[] p){ //only a pair can be rotated if (p[0].getY()==p[1].getY()){ //=>lie in same row if(p[1].getX() == p[0].getX() + 1){ //p[1] is on right => move p[1] SOUTH if(insertInBoardMap(p[1], p[0].getX(), p[0].getY()+1)){ //the cell below p[0] is empty & successful move has taken place //nullify previous position and update the status of puyo p[1] boardMap[p[1].getX()][p[1].getY()] = null; p[1].setX(p[0].getX()); p[1].setY(p[0].getY()+1); } } else if(p[0].getX() == p[1].getX() + 1) { //p[1] is on the left => move p[1] NORTH if(insertInBoardMap(p[1], p[0].getX(), p[0].getY()-1)){ boardMap[p[1].getX()][p[1].getY()] = null; p[1].setX(p[0].getX()); p[1].setY(p[0].getY()-1); } } }// horizontal case ends here else if(p[0].getX() == p[1].getX()){ //=> lie in the same column if(p[0].getY() == p[1].getY() + 1){ //p[1] is above p[0] => move p[1] EAST if(insertInBoardMap(p[1], p[0].getX()+1, p[0].getY())){ boardMap[p[1].getX()][p[1].getY()] = null; p[1].setX(p[0].getX() + 1); p[1].setY(p[0].getY()); } } else if(p[1].getY() == p[0].getY()+1){ //p[1] is below p[0] => move p[1] WEST if(insertInBoardMap(p[1], p[0].getX()-1, p[0].getY())){ boardMap[p[1].getX()][p[1].getY()] = null; p[1].setX(p[0].getX() - 1); p[1].setY(p[0].getY()); } } } }// end of the function /** * Holds all the puyos to be removed from the {@link puyopuyo.backEnd#boardMap boardMap} due to them forming a chain * of {@link puyopuyo.backEnd#popmin popmin} length. */ private Stack puyosToBePopped = new Stack(); /** * Holds the next set of puyos which have to be settled down and then checks * have to be made once they cannot move downwards anymore. */ private Stack nextSetOfPuyos = new Stack(); /** * It is the entry point for {@link puyopuyo.frontEnd# frontEnd}. * * <p>It also acts as 'manager', controlling the three methods that comprise the core logic: * <ul> * <li>{@link puyopuyo.backEnd#probe(puyo) probe(puyo)}</li> * <li>{@link puyopuyo.backEnd#popAndMarkDroppings(HashSet) popAndMarkDroppings(HashSet)}</li> * <li>{@link puyopuyo.backEnd#settleDown(puyo) settleDown(puyo)}</li> * </ul></p> * @param p Puyo for which chain has to be identified and popped from the board. */ public synchronized void popPuyos(puyo p[]){ //both of the puyos in the pair have settled down, check can begin with any puyo if(p.length == 0) return; //termination condition for recursion. no more puyos available to work on HashSet marked = new HashSet(); puyo[] temp; for(int i = 0; i<p.length; i++){ marked = probe(p[i]); if(marked.size()>=popmin) puyosToBePopped.push(new HashSet(marked)); marked.clear(); } while(!puyosToBePopped.isEmpty()){ temp = popAndMarkDroppings((HashSet)puyosToBePopped.pop()); if(temp.length > 0) nextSetOfPuyos.push(temp); } Iterator scan = nextSetOfPuyos.iterator();//iterator doesn't remove from stack. Popping is done in next loop while(scan.hasNext()){ temp = (puyo[])scan.next(); /*puyos are sorted in ascending order of getY() [popAndMarkDroppings returns a sorted array]. * The ones with greater value of Y need to be moved down before others otherwise they'll * block the path of puyos above them... eventually leaving some puyos hanging in mid air. */ for(int i=temp.length -1; i>=0; i--) settleDown(temp[i]); } while(!nextSetOfPuyos.isEmpty()) popPuyos((puyo[])nextSetOfPuyos.pop()); } /** * Settles the puyo passed as parameter down to the floor and also all the puyos above it. * @param p Puyo to be moved down. */ private synchronized void settleDown(puyo p){ int xPos = p.getX(); int yPos = p.getY(); //xPos and yPos are modified, xCopy and yCopy are used to check termination condition for recursion. while(p!=null && insertInBoardMap(p, xPos, yPos+1)){ p.setY(yPos+1); removeFromBoardMap(xPos, yPos); yPos++; } } /** * Holds the puyos to be popped from {@link puyopuyo.backEnd#boardMap boardMap} as they form chains of length >= {@link puyopuyo.backEnd#popmin popmin}. */ private HashSet removePuyos = new HashSet(); /** * Returns the set of puyos to be moved down because of chains formed by * puyo, being passed as the argument. * @param p Puyo for which chains on the board have to be identified. * @return Set of puyos to be removed from the boardMap. */ private synchronized HashSet probe(puyo p){ removePuyos.add(p); //****mark the puyo for which checks have to be done int nextX = p.getX(), nextY = p.getY(); int directions[]={-1, +1}; /* Description of the following loop: * This method is called recursively and proceeds forward in the correct * direction. Correct direction here is defined as that direction where * a puyo with same color exists. procession is done by calling the same * function with the new puyo (stored in temp). * First it probes the one to left and proceeds in that direction, if * there's a proper match. Then the one to right is checked. The above * 2 activities are done in the first for loop. * Likewise, the second for loop checks first below current puyo and * then the puyo above it. * In both the loops, if a matching puyo exists above, below or to left * or right, the MATCHED puyo is added to the Set which holds the *unique* * puyos to be removed. Then the control passes onto MATCHED puyo. * Because the set holds unique values only, the control doesnot allow * probing to proceed in direction of puyo already matched. Hence the * control finally goes towards any uncharted direction. * MATCHED puyo is added because when we reach end of a chain, the last * puyo in a chain will not be added because the puyo from which the * control came will already be in the set, hence not allowing the last * matched puyo to be added to the set. */ for (int i = 0 ; i < directions.length; i++){ nextX = p.getX()+directions[i]; if( (nextX >= 0 & nextX < puyosOnXAxis) && boardMap[nextX][nextY]!=null){ //boundary conditions if(p.getColor()==boardMap[nextX][nextY].getColor()){ if(removePuyos.add(boardMap[nextX][nextY])) //successful addition => temp has not been parsed previously, add it to set probe(boardMap[nextX][nextY]); //proceed forward with the check in its direction } } } nextX = p.getX(); //nextX was changed by above loop for (int i = 0 ; i < directions.length; i++){ nextY = p.getY()+directions[i]; if( (nextY >= 0 & nextY < puyosOnYAxis) && boardMap[nextX][nextY]!=null){ //boundary conditions if(p.getColor()==boardMap[nextX][nextY].getColor()){ if(removePuyos.add(boardMap[nextX][nextY])) //successful addition => temp has not been parsed previously, add it to set probe(boardMap[nextX][nextY]); //proceed forward with the check in temp puyo's direction } }//end of if checking boundary condition } return removePuyos; }//end of probe() /** * Pop the puyos marked by {@link puyopuyo.backEnd#probe(puyo) probe(puyo)} and also identifies all the puyos which have * to be moved down because of popping. * @param deletePuyos Puyos which have to be deleted from the boardMap. * @return Puyos which have to be moved down. */ private synchronized puyo[] popAndMarkDroppings(HashSet deletePuyos){ //:) pun intended /* it works on vectors and returns an array of puyos. *Vectors are being used because they are already synchronized, can be *converted to arrays after everything is done and can be resized dynamically */ Vector v = new Vector(); puyo temp; puyo puyoAboveTemp; Iterator i = deletePuyos.iterator(); while(i.hasNext()){//goin thru each element exactly once temp = (puyo)i.next(); //get an element from the set //popping routine// boardMap[temp.getX()][temp.getY()] = null; //remove temp from boardMap if not already removed score+=10; //increment score by 10 //---------------// //Marking routine// try{ do{ puyoAboveTemp = boardMap[temp.getX()][temp.getY()-1]; if( (puyoAboveTemp!=null && !v.contains(puyoAboveTemp)) & !deletePuyos.contains(puyoAboveTemp) ) //puyoAboveTemp has not been already marked v.add(puyoAboveTemp);//add it to set of puyos to be moved down temp = puyoAboveTemp; }while( temp!=null ); //puyo above the one being removed exists and is not the one to be popped } catch (ArrayIndexOutOfBoundsException e){ /* This happens in a very special case in which a pair falls down *and one among then is blocked at y=0 and the other falls down *making a chain in the coloumn which is filled till y=0. When *the chain pops, the above loop moves upward and on finding 'puyoAboveTemp' *above puyo at y=0, it reports this exception. because it is a *very special case, it need not be checked in the normal flow. * *When the exception occurs, the method should just continue with *popping the chain as all the puyos making up the column have been marked. *When exception has occured, control is already outside the loop and *should proceed normally with the outer while loop... */ puyoAboveTemp = null; } //---------------// temp = null; //puyoAboveTemp is null when control reaches here. }//end of while puyo[] movePuyosDown = new puyo[v.size()]; movePuyosDown = (puyo[])v.toArray(movePuyosDown); v.clear(); if(movePuyosDown.length > 1) Arrays.sort(movePuyosDown, new Comparator(){ public int compare(Object p1, Object p2){ if ( (p1!=null && !(p1 instanceof puyo)) & (p2!=null && !(p2 instanceof puyo)) ) throw new ClassCastException(); /*In case of one puyo being null, the one's that aint null is returned to be greater. If both are null, value corresponding to equality is returned*/ if( (p1 == null) & (p2 instanceof puyo) ) return -1; //once this evaluates to true, the control should not go further, thats y return statement is used here. else if ( (p1 instanceof puyo) & (p2 == null)) return 1; else if ( (p1 == null) & (p2 == null)) return 0; int retVal = 0; if ( ((puyo)p1).getY() < ((puyo)p2).getY() ) retVal = -1; else if ( ((puyo)p1).getY() > ((puyo)p2).getY()) retVal = 1; //if the two are equal, retVal = 0, which is the default value. return retVal; } } /* end of anonymous inner class*/ ); return movePuyosDown; } }