/* Tetris.java * Two-player Tetris * Released under the Ricky license: * You are free to do whatever you * want with this source, AS LONG AS * you learn something from it. * Author: Ricky C. * Last modified: 4/19/07 * School: [removed] * IDE used: Eclipse JDK v3.2.2 / Java SDK v1.6.0_01 * Purpose: To allow two players to play a game of Tetris. */import java.awt.*;import java.awt.event.*;import java.awt.geom.Rectangle2D;import java.util.Random;public class Tetris extends Frame implements Runnable,WindowListener,KeyListener { private static final long serialVersionUID = -6773388040204011141L; // GAME SETTINGS // public static final int FPS = 30; public static final int GAMEAREAWIDTH = 10; public static final int GAMEAREAHEIGHT = 20; public static final int GAMEAREABGCOLOR = 0xFFFFFF; public static final int GAMEAREABORDERCOLOR = 0xFFFFFF; public static final int BLOCKSIZE = 16; // STATE CONSTANTS // public static final int STATE_PRESSAKEY = 0; public static final int STATE_READY = 1; public static final int STATE_NORMAL = 2; public static final int STATE_PAUSE_ELIMINATINGLINES = 3; public static final int STATE_PAUSE_USER = 4; public static final int STATE_GAMEOVER = 5; // BLOCK IDS // public static final int BLOCK_I = 0; public static final int BLOCK_J = 1; public static final int BLOCK_L = 2; public static final int BLOCK_O = 3; public static final int BLOCK_S = 4; public static final int BLOCK_T = 5; public static final int BLOCK_Z = 6; Player player1; Player player2; int globalState; // BACK BUFFERED DRAWING VARIABLES // Thread drawThread; Graphics bbGraphics; Image bbImage; long lastUpdate; public static final Random randGen = new Random(System.currentTimeMillis()); // KEYPRESS BUFFER // // Notice type "Boolean[]", not "boolean[]" // This is done intentionally so that I can utilize the // null state that only exists in Boolean. Boolean[] keys; public static void main(String[] args) { new Tetris(); System.exit(0); } public Tetris() { this.setVisible(false); setUpWindow(); resetGame(); // START DRAWING drawThread.start(); try { drawThread.join(); } catch(Exception e) {} } /* sets up the Frame */ private void setUpWindow() { this.setTitle("Tetris"); this.setResizable(false); this.setBackground(Color.white); this.setForeground(Color.black); this.addWindowListener(this); this.addKeyListener(this); this.setVisible(true); this.setSize(getInsets().left+getInsets().right+GAMEAREAWIDTH*BLOCKSIZE*2+90,getInsets().top+getInsets().bottom+GAMEAREAHEIGHT*BLOCKSIZE); Dimension dim = getToolkit().getScreenSize(); Rectangle abounds = getBounds(); this.setLocation((dim.width - abounds.width) / 2, (dim.height - abounds.height) / 2); } /* clears the key buffer */ private void clearKeys() { if(keys==null) return; synchronized(keys) { for(int i=0;i<keys.length;i++) { keys[i] = Boolean.FALSE; } } } /* sets up the back buffer */ private void setUpBackBuffer() { if(bbImage==null) bbImage = this.createImage(this.getWidth(), this.getHeight()); if(drawThread==null) drawThread = new Thread(this); } /* resets variables, initializes everything */ private void resetGame() { if(keys==null) keys = new Boolean[256]; clearKeys(); if(bbImage != null) { synchronized(bbImage) { bbImage = null; } } setUpBackBuffer(); player1 = new Player(); player2 = new Player(); globalState = STATE_PRESSAKEY; } public void paint(Graphics g) { draw(g); } public void update(Graphics g) { draw(g); } /* back buffer thread's run() method */ public void run() { // main game loop // while(!Thread.currentThread().isInterrupted()) { lastUpdate = System.currentTimeMillis(); checkKeys(); processEvents(); repaint(); // sleep for up to (1000/FPS) milliseconds try {Thread.sleep(Math.max(1,lastUpdate+1000/FPS-System.currentTimeMillis()));} catch(Exception e) {} } } /* perform events based on key presses */ public void checkKeys() { if(keys==null) return; synchronized(keys) { if(keys[KeyEvent.VK_ESCAPE]==Boolean.TRUE) { this.dispose(); // exit } // game is waiting for both players to press a key if(globalState==STATE_PRESSAKEY) { if(keys[KeyEvent.VK_W]==Boolean.TRUE || keys[KeyEvent.VK_A]==Boolean.TRUE || keys[KeyEvent.VK_S]==Boolean.TRUE || keys[KeyEvent.VK_D]==Boolean.TRUE) player1.state = STATE_READY; if(keys[KeyEvent.VK_UP]==Boolean.TRUE || keys[KeyEvent.VK_LEFT]==Boolean.TRUE || keys[KeyEvent.VK_RIGHT]==Boolean.TRUE || keys[KeyEvent.VK_DOWN]==Boolean.TRUE) player2.state = STATE_READY; if(player1.state==STATE_READY && player2.state==STATE_READY) { player1.state = STATE_NORMAL; player2.state = STATE_NORMAL; globalState = STATE_NORMAL; } if(globalState==STATE_NORMAL) clearKeys(); } // game is over else if(globalState==STATE_GAMEOVER) { for(int i=0;i<keys.length;i++) if(keys[i]==Boolean.TRUE) resetGame(); } // game is in progress else if(globalState==STATE_NORMAL) { // if P is pressed if(keys[KeyEvent.VK_P]==Boolean.TRUE) { // pause the game and both players globalState = STATE_PAUSE_USER; player1.state = STATE_PAUSE_USER; player2.state = STATE_PAUSE_USER; keys[KeyEvent.VK_P] = null; return; } // if player 1 is playing, not waiting if(player1.state==STATE_NORMAL) { // if W (up) is pressed if(keys[KeyEvent.VK_W]==Boolean.TRUE) { player1.currBlock.rotate(false); keys[KeyEvent.VK_W] = null; } boolean keyPressed = false; // if A (left) is pressed if(keys[KeyEvent.VK_A]==Boolean.TRUE && player1.lastKeyPress+200<lastUpdate) { player1.currBlock.translate(-1,0); keyPressed = true; } // if D (right) is pressed if(keys[KeyEvent.VK_D]==Boolean.TRUE && player1.lastKeyPress+200<lastUpdate) { player1.currBlock.translate(1,0); keyPressed = true; } // if S (down) is pressed if(keys[KeyEvent.VK_S]==Boolean.TRUE && player1.lastKeyPress+200<lastUpdate) { if(player1.currBlock.translate(0,1)) player1.lastTick = lastUpdate; keyPressed = true; } if(keyPressed) player1.lastKeyPress = lastUpdate; if(keys[KeyEvent.VK_A]==Boolean.FALSE && keys[KeyEvent.VK_D]==Boolean.FALSE && keys[KeyEvent.VK_S]==Boolean.FALSE) player1.lastKeyPress = 0; } if(player2.state==STATE_NORMAL) { // if up is pressed if(keys[KeyEvent.VK_UP]==Boolean.TRUE) { player2.currBlock.rotate(false); keys[KeyEvent.VK_UP] = null; } boolean keyPressed = false; // if left is pressed if(keys[KeyEvent.VK_LEFT]==Boolean.TRUE && player2.lastKeyPress+200<lastUpdate) { player2.currBlock.translate(-1,0); keyPressed = true; } // if right is pressed if(keys[KeyEvent.VK_RIGHT]==Boolean.TRUE && player2.lastKeyPress+200<lastUpdate) { player2.currBlock.translate(1,0); keyPressed = true; } // if down is pressed if(keys[KeyEvent.VK_DOWN]==Boolean.TRUE && player2.lastKeyPress+200<lastUpdate) { if(player2.currBlock.translate(0,1)) player2.lastTick = lastUpdate; keyPressed = true; } if(keyPressed) player2.lastKeyPress = lastUpdate; if(keys[KeyEvent.VK_LEFT]==Boolean.FALSE && keys[KeyEvent.VK_RIGHT]==Boolean.FALSE && keys[KeyEvent.VK_DOWN]==Boolean.FALSE) player2.lastKeyPress = 0; } } // user has paused the game else if(globalState==STATE_PAUSE_USER) { // if P is pressed, resume game if(keys[KeyEvent.VK_P]==Boolean.TRUE) { globalState = STATE_NORMAL; player1.state = STATE_NORMAL; player2.state = STATE_NORMAL; keys[KeyEvent.VK_P] = null; } } } } /* eliminate one solid line or fill an empty line * (by dropping all spaces above it down by one) */ public boolean eliminateALine(Player plr) { int topLine = -1; // find the topmost non-empty row for(int y=0;y<GAMEAREAHEIGHT;y++) { for(int x=0;x<GAMEAREAWIDTH;x++) if(plr.gameAreaColors[y][x]!=GAMEAREABGCOLOR) topLine = y; if(topLine!=-1) break; } if(topLine==-1) // playing field is empty return false; else { int emptyLine = -1; // find an empty line below the topLine OUTERLOOP1: for(int y=topLine;y<GAMEAREAHEIGHT;y++) { for(int x=0;x<GAMEAREAWIDTH;x++) { if(plr.gameAreaColors[y][x]!=GAMEAREABGCOLOR) continue OUTERLOOP1; } emptyLine = y; break; } // if there is an empty line, shuffle everything down one if(emptyLine>-1) { for(int y=emptyLine;y>=topLine;y--) { for(int x=0;x<GAMEAREAWIDTH;x++) plr.gameAreaColors[y][x] = plr.gameAreaColors[y-1][x]; } } // else, search for a full line and remove it else { int fullLine = -1; // find a filled line OUTERLOOP2: for(int y=topLine;y<GAMEAREAHEIGHT;y++) { for(int x=0;x<GAMEAREAWIDTH;x++) { if(plr.gameAreaColors[y][x]==GAMEAREABGCOLOR) continue OUTERLOOP2; } fullLine = y; break; } if(fullLine==-1) { return false; } // a filled line was found, increment score and remove the line else { plr.incrementScore(1); for(int x=0;x<GAMEAREAWIDTH;x++) plr.gameAreaColors[fullLine][x] = GAMEAREABGCOLOR; } } } return true; } /* process events for a specific player */ private void processEvents(Player plr, long timeNow) { // if lines are being eliminated and it has been .2 seconds since the last elimination if(plr.state==STATE_PAUSE_ELIMINATINGLINES && plr.lastTick<timeNow-200) { // try to eliminate a line, if there is nothing left to do... if(!eliminateALine(plr)) { // ... then bring out the next block and check for game over plr.bringOutNextBlock(); if(plr.state==STATE_GAMEOVER) { if(player1.state==STATE_GAMEOVER && player2.state==STATE_GAMEOVER) globalState = STATE_GAMEOVER; } else plr.state=STATE_NORMAL; } plr.lastTick = timeNow; } // if the player is playing, and it has been tickInterval milliseconds since the last event else if(plr.state==STATE_NORMAL && plr.lastTick<timeNow-plr.tickInterval) { // try to move the current block down one, if it fails... if(!plr.currBlock.translate(0,1)) { // then the block must be at the bottom, so draw the block into the permanent // gameAreaColors and eliminate any necessary lines plr.gameAreaColors = plr.currBlock.draw(plr.gameAreaColors); plr.currBlock = null; plr.state = STATE_PAUSE_ELIMINATINGLINES; } plr.lastTick = timeNow; } } /* process events for both players */ public void processEvents() { // don't process events while paused if(globalState==STATE_PAUSE_USER) return; processEvents(player1,lastUpdate); processEvents(player2,lastUpdate); } /* draw the window's contents */ public void draw(Graphics g) { if(bbImage==null) return; // synchronize; bbImage must stay intact throughout this function synchronized(bbImage) { if(bbImage == null || g == null) return; // get our Graphics object to draw to bbGraphics = bbImage.getGraphics(); // clear the back buffer bbGraphics.clearRect(0,0,getWidth(),getHeight()); //---------------------------------- // draw each game area (consisting of the colored blocks) drawGameArea(bbGraphics,this.getInsets().left,this.getInsets().top,player1); drawGameArea(bbGraphics,this.getWidth()-this.getInsets().left-GAMEAREAWIDTH*BLOCKSIZE,this.getInsets().top,player2); // draw the players' scores bbGraphics.setColor(Color.black); bbGraphics.drawString("Player 1:",this.getInsets().left+GAMEAREAWIDTH*BLOCKSIZE+20,this.getInsets().top+20); bbGraphics.drawString("Player 2:",this.getInsets().left+GAMEAREAWIDTH*BLOCKSIZE+20,this.getInsets().top+210); drawStatus(bbGraphics, player1, 25); drawStatus(bbGraphics, player2, 215); if(globalState==STATE_GAMEOVER || globalState==STATE_PAUSE_USER || globalState==STATE_PRESSAKEY) { bbGraphics.setFont(new Font("Comic Sans MS",Font.PLAIN,12)); } if(globalState==STATE_GAMEOVER || globalState==STATE_PAUSE_USER) { bbGraphics.setColor(Color.black); String msg = (globalState==STATE_GAMEOVER)?"Game over":((globalState==STATE_PAUSE_USER)?"[PAUSED]":""); Rectangle2D fontBounds = bbGraphics.getFontMetrics().getStringBounds(msg,bbGraphics); bbGraphics.fillRect((int)(this.getWidth()/2-fontBounds.getCenterX()),(int)(this.getHeight()/2-fontBounds.getCenterY()),(int)fontBounds.getWidth(),(int)fontBounds.getHeight()); bbGraphics.setColor(Color.white); bbGraphics.drawString(msg,(int)(this.getWidth()/2-fontBounds.getCenterX()),(int)(this.getHeight()/2+fontBounds.getHeight())); } if(globalState==STATE_PRESSAKEY) { //if(globalState==STATE_PRESSAKEY) String msg1 = ("Player 1: "+((player1.state==STATE_READY)?"Ready":"Press an arrow key")); Rectangle2D fontBounds1 = bbGraphics.getFontMetrics().getStringBounds(msg1,bbGraphics); bbGraphics.setColor(Color.black); bbGraphics.fillRect(GAMEAREAWIDTH*BLOCKSIZE/2+getInsets().left-(int)fontBounds1.getCenterX(),(int)this.getHeight()/2-(int)fontBounds1.getCenterY(),(int)fontBounds1.getWidth(),(int)fontBounds1.getHeight()); bbGraphics.setColor(Color.white); bbGraphics.drawString(msg1, GAMEAREAWIDTH*BLOCKSIZE/2+getInsets().left-(int)fontBounds1.getCenterX(),(int)this.getHeight()/2+(int)fontBounds1.getHeight()); String msg2 = ("Player 2: "+((player2.state==STATE_READY)?"Ready":"Press an arrow key")); Rectangle2D fontBounds2 = bbGraphics.getFontMetrics().getStringBounds(msg2,bbGraphics); bbGraphics.setColor(Color.black); bbGraphics.fillRect(getWidth()-getInsets().right-GAMEAREAWIDTH*BLOCKSIZE/2-(int)fontBounds2.getCenterX(),(int)this.getHeight()/2-(int)fontBounds2.getCenterY(),(int)fontBounds2.getWidth(),(int)fontBounds2.getHeight()); bbGraphics.setColor(Color.white); bbGraphics.drawString(msg2, getWidth()-getInsets().right-GAMEAREAWIDTH*BLOCKSIZE/2-(int)fontBounds2.getCenterX(),(int)this.getHeight()/2+(int)fontBounds2.getHeight()); } //---------------------------------- bbGraphics.dispose(); g.drawImage(bbImage,0,0,null); } } /* draw the player's score */ public void drawStatus(Graphics g, Player plr, int topOffset) { g.setColor(Color.black); g.setFont(new Font("Courier New",Font.PLAIN,12)); // get bounds of a 5-character-wide string // (Courier New is a fixed-spacing font so any 5 characters will work, in this case "12345") Rectangle2D fontBounds1 = g.getFontMetrics().getStringBounds("12345", g); // draw the score box and the score in it // calculate coordinates based on insets (borders), size of each block, and number of blocks int left = this.getInsets().left+GAMEAREAWIDTH*BLOCKSIZE+20; int top = this.getInsets().top+topOffset; int right = left+50; int bottom = top+(int)fontBounds1.getHeight()*2+20; g.drawRect(left,top,right-left,bottom-top); g.drawString("Score",(int)(left+((right-left)/2)-(fontBounds1.getWidth()/2)),(int)(top+fontBounds1.getHeight())); top+=(int)fontBounds1.getHeight()+3; bottom+=(int)fontBounds1.getHeight()+3; g.drawString(""+plr.score,(int)(left+((right-left)/2)-(fontBounds1.getWidth()/2)),(int)(top+fontBounds1.getHeight())); // draw the next block preview top += (int)fontBounds1.getHeight()+30; bottom = top+4*8; // left = left; right = left+4*8; g.setColor(Color.black); g.drawRect(left,top,right-left+2,bottom-top+2); left++; top++; for(int y=0;y<plr.nextBlockPreview.length;y++) for(int x=0;x<plr.nextBlockPreview[0].length;x++) { g.setColor(new Color(plr.nextBlockPreview[y][x])); g.fillRect(x*8+left+1,y*8+top+1,8-2,8-2); } } /* draw a game area */ public void drawGameArea(Graphics g, int xc, int yc, Player plr) { // temporary game area, which combines the permanent blocks with the current (dynamic) block int[][] tempAreaColors; if(plr.currBlock!=null) tempAreaColors = plr.currBlock.draw(plr.gameAreaColors); else tempAreaColors = plr.gameAreaColors; // loop through and draw each block for(int x = 0;x<GAMEAREAWIDTH;x++) { for(int y=0;y<GAMEAREAHEIGHT;y++) { g.setColor(new Color(GAMEAREABORDERCOLOR)); g.fillRect(xc+(x*BLOCKSIZE),yc+(y*BLOCKSIZE),BLOCKSIZE,BLOCKSIZE); g.setColor(new Color(tempAreaColors[y][x])); g.fillRect(xc+(x*BLOCKSIZE)+1,yc+(y*BLOCKSIZE)+1,BLOCKSIZE-2,BLOCKSIZE-2); } } // draw a black border around the game area g.setColor(Color.black); g.drawRect(xc,yc,GAMEAREAWIDTH*BLOCKSIZE,GAMEAREAHEIGHT*BLOCKSIZE); } /* get a new block based on the block ID * (used for mapping a random number to a block type) */ public Block getNewBlock(int type,Player plr) { switch(type) { case BLOCK_I: return new BlockI(plr); case BLOCK_J: return new BlockJ(plr); case BLOCK_L: return new BlockL(plr); case BLOCK_O: return new BlockO(plr); case BLOCK_S: return new BlockS(plr); case BLOCK_T: return new BlockT(plr); case BLOCK_Z: return new BlockZ(plr); default: return null; } } /* WindowListener methods */ public void windowActivated(WindowEvent e) {} public void windowClosed(WindowEvent e) {} public void windowClosing(WindowEvent e) { drawThread.interrupt(); this.dispose(); System.exit(0); } public void windowDeactivated(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowOpened(WindowEvent e) {} /* KeyListener methods */ public void keyTyped(KeyEvent e) {} // Note: I use "null" as a state between TRUE and FALSE. // "null" signifies the time in which the key's corresponding event has // been handled by the event processing methods (above) but it is not yet released. // Because Windows operating systems send multiple keyPressed events when a key is held, // keyPressed will only set the key to TRUE if it is already FALSE (released). public void keyPressed(KeyEvent e) { if(keys==null) return; synchronized(keys) { if(keys[e.getKeyCode()]==Boolean.FALSE) keys[e.getKeyCode()] = Boolean.TRUE; } } public void keyReleased(KeyEvent e) { if(keys==null) return; synchronized(keys) { keys[e.getKeyCode()] = Boolean.FALSE; } } /* represents one player, of which there are two */ protected class Player { // the game area public int[][] gameAreaColors; // the interval between each time the current block // is shifted down one space (in milliseconds) public int tickInterval = 1000; // one of the possible STATE_* constants public int state; // the player's score (number of rows filled) public int score; // current block, next block, and preview of the next block public Block currBlock; public int nextBlockType; public int[][] nextBlockPreview; public long lastKeyPress; // used to perform key press responses public long lastTick; // used to perform game events public Player() { this.score = 0; // create the game area, make it "empty" by filling with // the GAMEAREABGCOLOR this.gameAreaColors = new int[GAMEAREAHEIGHT][GAMEAREAWIDTH]; for(int x=0;x<GAMEAREAWIDTH;x++) for(int y=0;y<GAMEAREAHEIGHT;y++) this.gameAreaColors[y][x] = GAMEAREABGCOLOR; // set the next block this.nextBlockType = randGen.nextInt(7); this.bringOutNextBlock(); // enter the waiting-for-key-press state this.state = STATE_PRESSAKEY; } /* increment the player's score by amount and calculate the tickInterval */ public void incrementScore(int amount) { this.score += amount; this.tickInterval = 1000-(score*30); } /* move the next block into the current one, and then select a random next block */ public void bringOutNextBlock() { this.currBlock = getNewBlock(this.nextBlockType,this); this.nextBlockType = randGen.nextInt(7); // generate a block preview for the next block Block tempBlk = getNewBlock(this.nextBlockType,this); this.nextBlockPreview = new int[(tempBlk.localSpace).length][(tempBlk.localSpace)[0].length]; for(int y=0;y<(tempBlk.localSpace).length;y++) for(int x=0;x<(tempBlk.localSpace)[0].length;x++) this.nextBlockPreview[y][x] = (((tempBlk.localSpace)[y][x])?tempBlk.color:GAMEAREABGCOLOR); // enter the normal state, and then check to see if the block collides; // if so, enter the game over state this.state = STATE_NORMAL; if(!currBlock.translate(0,0)) this.state = STATE_GAMEOVER; } } /* base class for a block */ abstract protected class Block { // rotates the block // if CCW is true, rotates it counter-clockwise; else, rotates it clockwise // if the block flips, CCW is ignored // see Figure 3 for the distinction between rotating and flipping public synchronized boolean rotate(boolean CCW) { if(flips) { // if it is able to flip without colliding... if(!collision(flippedSpace,owner.gameAreaColors)) { // ... then swap this.localSpace and this.flippedSpace boolean[][] temp = this.localSpace; this.localSpace = flippedSpace; this.flippedSpace = temp; return true; } else { return false; } } else { // it does not flip; rotate it boolean[][] temp = new boolean[localSpace.length][localSpace[0].length]; if(CCW) { for(int y=0;y<localSpace.length;y++) for(int x=0;x<localSpace[y].length;x++) temp[y][x] = this.localSpace[x][3-y]; } else { for(int y=0;y<localSpace.length;y++) for(int x=0;x<localSpace[y].length;x++) temp[y][x] = this.localSpace[(localSpace.length-1)-x][y]; } // if the new rotated block collides with existing blocks, don't rotate it if(collision(temp,owner.gameAreaColors)) return false; else { this.localSpace = temp; return true; } } } /* check for a collision between the locSp (local space) and gAC (gameAreaColors) */ protected synchronized boolean collision(boolean[][] locSp, int[][] gAC) { for(int y=0;y<locSp.length;y++) { for(int x=0;x<locSp[y].length;x++) { if(locSp[y][x]) if(y+yloc<0 || x+xloc<0 || y+yloc>gAC.length-1 || x+xloc>gAC[0].length-1 || gAC[y+yloc][x+xloc]!=GAMEAREABGCOLOR) return true; } } return false; } /* try to move the block (relx, rely) relative units * relx and rely are in screen coordinates; positive Y is downward */ public synchronized boolean translate(int relx, int rely) { //if(xloc+relx<0 || xloc+relx+localSpace[0].length>inst.gameAreaColors[0].length || yloc+rely<0 || yloc+rely+localSpace.length>inst.gameAreaColors.length) // return false; if(owner==null) return true; for(int y=0;y<localSpace.length;y++) { for(int x=0;x<localSpace[y].length;x++) { if(localSpace[y][x]) if(y+yloc+rely<0 || y+yloc+rely>owner.gameAreaColors.length-1 || x+xloc+relx<0 || x+xloc+relx>owner.gameAreaColors[0].length-1 || owner.gameAreaColors[y+yloc+rely][x+xloc+relx]!=GAMEAREABGCOLOR) return false; } } xloc+=relx; yloc+=rely; return true; } /* draw the block onto gameAreaColors, and then return the result */ public synchronized int[][] draw(int[][] gameAreaColors) { int[][] temp = new int[gameAreaColors.length][gameAreaColors[0].length]; for(int i=0;i<temp.length;i++) temp[i] = (int[])gameAreaColors[i].clone(); for(int y=0;y<localSpace.length;y++) { for(int x=0;x<localSpace[y].length;x++) { if(localSpace[y][x]) temp[y+yloc][x+xloc] = color; } } return temp; } public Block(Player onr) { this.flips = false; this.owner = onr; this.xloc = 3; this.yloc = 0; } public abstract int getType(); public int color; public boolean[][] localSpace; public int xloc,yloc; protected Player owner; protected boolean flips; protected boolean[][] flippedSpace; } /* * Each of the seven blocks are created below. * They extend the constructor to set up local space and, if necessary, flipped space * and also color. The getType() functionality is not currently used. */ // [][][][] // protected class BlockI extends Block { public BlockI(Player onr) { super(onr); this.flips = true; this.localSpace = new boolean[][] { {false,false,false,false}, {true,true,true,true}, {false,false,false,false}, {false,false,false,false} }; this.flippedSpace = new boolean[][] { {false,true,false,false}, {false,true,false,false}, {false,true,false,false}, {false,true,false,false} }; this.color = 0x33FFFF; } public final int getType() { return BLOCK_I; } } // [] // [][][] protected class BlockJ extends Block { public BlockJ(Player onr) { super(onr); this.localSpace = new boolean[][] { {true,false,false}, {true,true,true}, {false,false,false}, }; this.color = 0x0000FF; } public final int getType() { return BLOCK_J; } } // [][][] // [] protected class BlockL extends Block { public BlockL(Player onr) { super(onr); this.localSpace = new boolean[][] { {false,false,false}, {true,true,true}, {true,false,false} }; this.color = 0xFF9900; } public final int getType() { return BLOCK_L; } } // [][] // [][] protected class BlockO extends Block { public BlockO(Player onr) { super(onr); this.localSpace = new boolean[][] { {true,true}, {true,true} }; this.color = 0xFFFF00; } public final int getType() { return BLOCK_O; } } // [][] // [][] protected class BlockS extends Block { public BlockS(Player onr) { super(onr); this.flips = true; this.localSpace = new boolean[][] { {false,true,true}, {true,true,false}, {false,false,false} }; this.flippedSpace = new boolean[][] { {false,true,false}, {false,true,true}, {false,false,true} }; this.color = 0x00FF00; } public final int getType() { return BLOCK_S; } } // [] // [][][] protected class BlockT extends Block { public BlockT(Player onr) { super(onr); this.localSpace = new boolean[][] { {false,true,false}, {true,true,true}, {false,false,false} }; this.color = 0x9900FF; } public final int getType() { return BLOCK_T; } } // [][] // [][] protected class BlockZ extends Block { public BlockZ(Player onr) { super(onr); this.flips = true; this.localSpace = new boolean[][] { {true,true,false}, {false,true,true}, {false,false,false}, }; this.flippedSpace = new boolean[][] { {false,false,true}, {false,true,true}, {false,true,false} }; this.color = 0xFF0000; } public final int getType() { return BLOCK_Z; } }}