炫彩的俄罗斯方块

来源:互联网 发布:linux 日历 编辑:程序博客网 时间:2024/04/28 10:23



TetrisDemo.java

package com.ubird.demo;import java.awt.BorderLayout;import javax.swing.JFrame;import javax.swing.SwingUtilities;import com.ubird.tetris.ui.GamePanel;import com.ubird.tetris.ui.ScorePanel;import com.ubird.ui.UFrame;import com.ubird.ui.event.RepeatingReleasedEventsFixer;public class TetrisDemo {public final static String VERSION = "V0.8.0";public final static String AUTHOR = "";public static void main(String[] args) {new RepeatingReleasedEventsFixer().install();SwingUtilities.invokeLater(new Runnable() {public void run() {UFrame frame = new UFrame("俄罗斯方块 " + VERSION+ "  By " + AUTHOR);ScorePanel scorePanel = new ScorePanel(200, 0);GamePanel gamePanel = new GamePanel(18, 32, 20, scorePanel);frame.getContentPane().add(gamePanel, BorderLayout.CENTER);frame.getContentPane().add(scorePanel, BorderLayout.EAST);frame.pack();frame.setVisible(true);frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setResizable(false);gamePanel.requestFocus();}});}}


ParticleNode.java

package com.ubird.particle;import java.awt.AlphaComposite;import java.awt.Color;import java.awt.Composite;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.Image;import java.awt.geom.AffineTransform;import java.util.Random;public class ParticleNode {private int initLife;private int life;private int initX;private int x;private int initY;private int y;private int width;private int height;private float angle;private float vr;private float vx;private float vy;private float ax;private float ay;private float initVy;private float initVx;private Image texture;private Composite[] alaphaComposite = new Composite[] {AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f) };private int type;private int initDelay;private int delay;private static Random random = new Random();public final static int TYPE_ONCE = 0;public final static int TYPE_CYCLE = 1;public final static int TYPE_ONCE_AND_NOT_DISPLAY_FIRST = 2;public ParticleNode(int x, int y, int width, int height, float vx,float vy, float vr, float ax, float ay, int life, int delay,int type) {this.x = 0;this.y = 0;this.initX = x;this.initY = y;this.width = width;this.height = height;this.vr = vr;this.vx = vx;this.vy = vy;this.initVx = vx;this.initVy = vy;this.ax = ax;this.ay = ay;this.life = type == TYPE_ONCE_AND_NOT_DISPLAY_FIRST ? 0 : life;this.initLife = life;this.type = type;this.delay = delay;this.initDelay = delay;}public void draw(Graphics g) {if (!isNeedDraw()) {return;}Graphics2D g2d = (Graphics2D) g.create();// Color color = new Color(255*(initLife-life)/initLife, 255, 0);g2d.setComposite(alaphaComposite[(initLife - life)* (alaphaComposite.length - 1) / initLife]);if (texture != null) {// g2d.setTransform(AffineTransform.getRotateInstance(20));// g2d.setTransform(AffineTransform.getTranslateInstance(x+initX,// y+initY));g2d.rotate(angle, x + initX + width / 2, y + initY + height / 2);// g2d.drawImage(texture, x+initX, y+initY, x+initX+width,// y+initY+height, 10, 354, 100, 434, null);g2d.drawImage(texture, x + initX, y + initY, x + initX + width, y+ initY + height, 13, 230, 93, 305, null);} else {Color color = new Color(125 + 125 * (life) / initLife, 255* (initLife - life) / initLife, 0);g2d.setColor(color);g2d.rotate(angle, x + initX + width / 2, y + initY + height / 2);g2d.fillOval(x + initX, y + initY, width, height);}g2d.dispose();}public void setTexture(Image texture) {this.texture = texture;}public void update(int time) {if (!isNeedUpdate()) {return;}if (this.delay > 0) {this.delay--;return;}life--;vx += ax;vy += ay;angle += vr;x += vx;y += vy;if (life == 0 && type == TYPE_CYCLE)init(initX, initY);}private boolean isNeedDraw() {return this.delay <= 0 && this.life > 0;}private boolean isNeedUpdate() {return this.life > 0 || life == 0 && type != TYPE_ONCE;}public void update() {update(0);}public void init(int x, int y) {this.x = 0;this.y = 0;this.initX = x;this.initY = y;this.vx = initVx;this.vy = initVy;this.life = initLife;this.delay = initDelay;}public void initY(int y) {init(initX, y);}enum PathCalculator {YunJiaSu() {@Overridepublic float calX(float vx0, float ax, int time) {return vx0 + ax * time / 80f;}@Overridepublic float calY(float vy0, float ay, int time) {return vy0 + ay * time / 80f;}};public abstract float calX(float vx0, float vxa, int time);public abstract float calY(float vy0, float vya, int time);}}


Particles.java

package com.ubird.particle;import java.awt.Graphics;import java.awt.Image;import java.io.IOException;import java.util.ArrayList;import java.util.List;import java.util.Random;import javax.imageio.ImageIO;public abstract class Particles {private List<ParticleNode> particleNode;public Particles() {particleNode = getParticleNodes();}public Particles(int x, int y) {particleNode = getParticleNodes(x, y);}protected abstract List<ParticleNode> getParticleNodes();protected abstract List<ParticleNode> getParticleNodes(int x, int y);public static Particles getInstance(int x, int y) {return new Particles1(x, y);}public void init(int x, int y) {if (particleNode != null) {for (ParticleNode node : particleNode) {node.init(x, y);}}}public void init(int y) {if (particleNode != null) {for (ParticleNode node : particleNode) {node.initY(y);}}}public void update(int time) {if (particleNode != null) {for (ParticleNode node : particleNode) {node.update(time);}}}public void draw(Graphics g) {if (particleNode != null) {for (ParticleNode node : particleNode) {node.draw(g);}}}static class Particles1 extends Particles {private final float ANGLE = (float) (2 * Math.PI);private static Random random = new Random();public Particles1(int x, int y) {super(x, y);}protected List<ParticleNode> getParticleNodes() {return getParticleNodes(0, 0);}protected List<ParticleNode> getParticleNodes(int x, int y) {int num = 400;int v0 = 1;int rangX = 400;int rangY = 20;List<ParticleNode> list = new ArrayList<ParticleNode>(num);Image texture = null;try {texture = ImageIO.read(getClass().getClassLoader().getResource("com/ubird/ui/res/Tetris.png"));} catch (IOException e) {e.printStackTrace();}for (int i = 0; i < num; i++) {int fixX = random.nextInt(rangX) - rangX / 2;int fixY = 0;// random.nextInt(rangY) - rangY/2;float ang = ANGLE * i / num;float vx = (float) (v0 * Math.sin(ang));float vy = (float) (v0 * Math.cos(ang));// vy = Math.abs(vy);vx = 0;int size = 10 + random.nextInt(10);int life = 35;int delay = random.nextInt(life);ParticleNode particleNode2 = new ParticleNode(x + fixX, y+ fixY, size, size, vx, vy, ANGLE / 240, 0, vy / 80,life, delay,ParticleNode.TYPE_ONCE_AND_NOT_DISPLAY_FIRST);particleNode2.setTexture(texture);list.add(particleNode2);}return list;}}}



TetrisBlock.java

package com.ubird.tetris.block;import java.awt.Graphics;import java.util.Arrays;import java.util.Random;public class TetrisBlock {public final static TetrisBlock POINT = new TetrisBlock(new int[][][]{{{0,0,1,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}});public final static TetrisBlock SQUARE = new TetrisBlock(new int[][][]{{{0,1,1,0},{0,1,1,0},{0,0,0,0},{0,0,0,0}}});public final static TetrisBlock LINE = new TetrisBlock(new int[][][]{{{0,0,0,0},{1,1,1,1},{0,0,0,0},{0,0,0,0}}, {{0,1,0,0},{0,1,0,0},{0,1,0,0},{0,1,0,0}}});public final static TetrisBlock Z = new TetrisBlock(new int[][][]{{{0,0,0,0},{1,1,0,0},{0,1,1,0},{0,0,0,0}}, {{0,1,0,0},{1,1,0,0},{1,0,0,0},{0,0,0,0}}});public final static TetrisBlock VZ = new TetrisBlock(new int[][][]{{{0,0,0,0},{0,1,1,0},{1,1,0,0},{0,0,0,0}}, {{1,0,0,0},{1,1,0,0},{0,1,0,0},{0,0,0,0}}});public final static TetrisBlock T = new TetrisBlock(new int[][][]{{{0,0,0,0},{1,1,1,0},{0,1,0,0},{0,0,0,0}}, {{0,1,0,0},{1,1,0,0},{0,1,0,0},{0,0,0,0}},{{0,1,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}}, {{0,1,0,0},{0,1,1,0},{0,1,0,0},{0,0,0,0}}});public final static TetrisBlock VL = new TetrisBlock(new int[][][]{{{1,0,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}}, {{0,1,1,0},{0,1,0,0},{0,1,0,0},{0,0,0,0}},{{0,0,0,0},{1,1,1,0},{0,0,1,0},{0,0,0,0}}, {{0,1,0,0},{0,1,0,0},{1,1,0,0},{0,0,0,0}}});public final static TetrisBlock L = new TetrisBlock(new int[][][]{{{0,0,1,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}}, {{0,1,0,0},{0,1,0,0},{0,1,1,0},{0,0,0,0}},{{0,0,0,0},{1,1,1,0},{1,0,0,0},{0,0,0,0}}, {{1,1,0,0},{0,1,0,0},{0,1,0,0},{0,0,0,0}}});static TetrisBlock[] ALL_BLOCKS = {POINT, SQUARE, LINE, Z,VZ, T, L, VL};//public final static TetrisBlock SUPER_LINE = new TetrisBlock(new int[][][]{{{0,0,0,0},{1,1,1,1,1,1},{1,1,1,1,1,1},{0,0,0,0}}, {{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}}});//static TetrisBlock[] ALL_BLOCKS = {SUPER_LINE};private final  int[][][] dirShapes;private int dir;private int x;private int y;private final static Random rand = new Random();TetrisBlock(TetrisBlock block){this.dir = block.dir;this.x = block.x;this.y = block.y;this.dirShapes = block.dirShapes;}TetrisBlock(int[][][] dirShapes){this.dirShapes = dirShapes;}public void init(int x, int y){this.x = x;this.y = y;}public synchronized void down() {y++;}public int[][] getShap(){return dirShapes[dir];}public int getXInWorld(){return x;}public int getYInWorld(){return y;}public int[][] rotate(){dir = (dir+1)%dirShapes.length;return dirShapes[dir];}public void draw(Graphics g, int gridSize) {int[][] shap = getShap();for(int i=0; i<shap.length; i++){for(int j=0; j<shap[i].length; j++){if(shap[i][j] == 1){g.fill3DRect((j+x)*gridSize, (i+y)*gridSize, gridSize, gridSize, false);}}}}public static TetrisBlock random() {return ALL_BLOCKS[rand.nextInt(ALL_BLOCKS.length)];}public void left() {x--;}public void right() {x++;}public TetrisBlock copyDown() {TetrisBlock tetrisBlock = new TetrisBlock(this);tetrisBlock.down();return tetrisBlock;}public TetrisBlock copyLeft() {TetrisBlock tetrisBlock = new TetrisBlock(this);tetrisBlock.left();return tetrisBlock;}public TetrisBlock copyRight() {TetrisBlock tetrisBlock = new TetrisBlock(this);tetrisBlock.right();return tetrisBlock;}public TetrisBlock copyRotate() {TetrisBlock tetrisBlock = new TetrisBlock(this);tetrisBlock.rotate();return tetrisBlock;}public TetrisBlock copy() {TetrisBlock tetrisBlock = new TetrisBlock(this);return tetrisBlock;}public String toString(){StringBuilder sb = new StringBuilder();for(int i=0; i<dirShapes.length; i++){sb.append(i).append(":[");for(int j=0; j<dirShapes[i].length; j++){sb.append(Arrays.toString(dirShapes[i][j])).append('\n');}sb.append("]\n");}return "TetrisBlock[\n" +"   x: " + x + "\n" +"   y: " + y + "\n" +"   dir: " + dir + "\n" +"   shape: " + sb.toString() + "\n" +"]";}}


GamePanel.java

package com.ubird.tetris.ui;import java.awt.AlphaComposite;import java.awt.Color;import java.awt.Composite;import java.awt.Dimension;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.RenderingHints;import java.awt.event.KeyEvent;import java.awt.event.KeyListener;import java.util.Collections;import java.util.LinkedList;import java.util.List;import javax.swing.JPanel;import com.ubird.particle.Particles;import com.ubird.tetris.block.TetrisBlock;import com.ubird.ui.DrawUtil;public class GamePanel extends JPanel implements KeyListener {private static final long serialVersionUID = -2552383962955502906L;protected volatile static int FPS = 60;protected volatile static long SPF = 1000 / FPS;private static final int DATA_EMPTY = 0; // 空数据private static final int DATA_BLOCK = 1; // 是方块private final int[][] data; // 游戏数据private int width;private int height;private int blockXNum; // 列数private int blockYNum; // 行数private int gridSize; // 格子大小private static final Color FALLING_BLOCK_COLOR = new Color(255, 255, 50);TetrisBlock fallingBlock = TetrisBlock.random(); // 正在下落的方块TetrisBlock nextBlock = null; // 下一个方块public static final int Y_SPEED_INIT = 20;public static final int Y_SPEED_ACCE = 15;public static final int Y_SPEED_ACCE_MAX = 5;private volatile int ySpeed = Y_SPEED_INIT; // 越小、速度越大public int counter = 0;private ScorePanel scorePanel;private int[] disLine; // 可以消除的行,用于做动画特效private int disIndex;private Composite[] disComposite = new Composite[] {AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f),AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f) };private volatile boolean pause = false;private int pauseIndex = 0;private Color pauseShadowColor = new Color(255, 0, 0);private Color pauseFontColor = new Color(255, 200, 200);private boolean gameOver = false;private int gameOverIndex = 0;private Particles ps = Particles.getInstance(200, 10);public GamePanel(int blockXNum, int blockYNum, int gridSize,ScorePanel scorePanel) {this.blockXNum = blockXNum;this.blockYNum = blockYNum;this.gridSize = gridSize;width = blockXNum * gridSize;height = blockYNum * gridSize;setPreferredSize(new Dimension(width, height));data = new int[blockYNum][blockXNum];fallingBlock.init(blockXNum / 2, 0);this.scorePanel = scorePanel;nextBlock = gererateNextBlock();addKeyListener(this);startRepaintThread();ps = Particles.getInstance(width / 2, height / 2);}private void restart() {fallingBlock = TetrisBlock.random();fallingBlock.init(blockXNum / 2, 0);scorePanel.clearScore();nextBlock = gererateNextBlock();disLine = null;disIndex = 0;for (int i = 0; i < data.length; i++) {for (int j = 0; j < data[i].length; j++) {data[i][j] = DATA_EMPTY;}}gameOver = false;}private void startRepaintThread() {new Thread(new Runnable() {public void run() {try {long start = System.currentTimeMillis();while (true) {Thread.sleep(SPF);start = System.currentTimeMillis() - start;updateData((int) start);start = System.currentTimeMillis();repaint();scorePanel.repaint();}} catch (InterruptedException e) {e.printStackTrace();}}}).start();}private void updateData(int duration) {if (fallingBlock != null) {if (isNeedDrawDisLine() || pause || gameOver) { // 正在播消行动画的话,不用更新数据return;}ps.update(duration);if (isTime2Update()) {if (isCanDown(fallingBlock)) { // 如果可以下落,继续下落fallingBlock.down();} else {int[] fixFallingBlock = fixFallingBlock(); // 固定方块disLine = fixFallingBlock;}}}}private void clearSuccLine(int[] clearLine) {for (int i = 0; i < clearLine.length; i++) {int to = i == 0 ? 0 : clearLine[i - 1];moveDownData(data, clearLine[i], 0);}}/** * 将制定数组的数据,从第from-1行到第to行往下移动一行 *  * @param data * @param from * @param to */private void moveDownData(int[][] data, int from, int to) {if (from <= to)throw new IllegalArgumentException("From should be larger than to");for (int i = from; i >= to; i--) {for (int j = 0; j < data[i].length; j++) {data[i][j] = i - 1 >= 0 ? data[i - 1][j] : 0;}}}private boolean isTime2Update() {if (counter++ == ySpeed) {counter = counter % ySpeed;return true;} elsereturn false;}/** * 固定方块 *  * @param fallingBlock */private int[] fixFallingBlock() {int[][] shap = fallingBlock.getShap();int x = fallingBlock.getXInWorld();int y = fallingBlock.getYInWorld();List<Integer> disLineNo = new LinkedList<Integer>(); // 消除的行for (int i = 0; i < shap.length; i++) {boolean isChange = false;for (int j = 0; j < shap[i].length; j++) {if (i + y < data.length && j + x >= 0 && j + x < data[i].length) {data[i + y][j + x] |= shap[i][j];isChange = true;}}if (isChange && isDisLine(i + y)) {disLineNo.add(i + y);}}fallingBlock = nextBlock;fallingBlock.init(blockXNum / 2, 0);nextBlock = gererateNextBlock();int[] temp = new int[disLineNo.size()];if (temp.length > 0)Collections.sort(disLineNo);int i = 0;for (Integer no : disLineNo) {temp[i++] = no;}checkIsGameOver();return temp;}private void checkIsGameOver() {for (int i = 0; i < data[0].length; i++) {if (data[0][i] == DATA_BLOCK) {gameOver();break;}}}private void gameOver() {gameOver = true;gameOverIndex = data.length - 1;fallingBlock = null;}private TetrisBlock gererateNextBlock() {TetrisBlock random = TetrisBlock.random();scorePanel.setNextBlock(random);return random;}/** * 指定行是否可以消除 *  * @param line * @return */private boolean isDisLine(int line) {for (int i = 0; i < data[line].length; i++) {if (data[line][i] == DATA_EMPTY)return false;}return true;}public void paintComponent(Graphics g) {super.paintComponent(g);paintData(g);paintFallingBlock(g);if (isNeedDrawDisLine()) {paintDisLine(g);}if (pause) {paintPause(g);}if (gameOver) {paintGameOver(g);}ps.draw(g);}private void paintGameOver(Graphics g) {gameOverIndex--;gameOverIndex = Math.max(0, gameOverIndex);}private void paintPause(Graphics g) {String pauseInfos = "Press [Enter] or [P] to continue...";int length = pauseInfos.length();String pauseInfo = pauseInfos;// pauseInfos.substring(0, pauseIndex);if (pauseIndex > length / 2)DrawUtil.drawShadowString(g, pauseInfo, 18, 50, 100, 3,pauseShadowColor, pauseFontColor);pauseIndex = (pauseIndex + 1) % length;}private void paintDisLine(Graphics g) {if (disLine != null) {disIndex = (disIndex + 1) % disComposite.length;Graphics2D g2d = (Graphics2D) g.create();g2d.setComposite(disComposite[disIndex]);g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);for (int i = 0; i < disLine.length; i++) {paintDisLine(g2d, disLine[i], disIndex, 4);}if (disLine.length > 0)ps.init(disLine[0] * gridSize);g2d.dispose();if (disIndex == 0) {clearSuccLine(disLine);scorePanel.addScore(disLine);disLine = null;}}}private void paintDisLine(Graphics2D g2d, int lineNo, int disIndex,int thick) {for (int i = 0; i < thick; i++) {int tempY = lineNo * gridSize + i;g2d.draw3DRect(i, tempY, width - i * 2, gridSize - i * 2, false);}}private boolean isNeedDrawDisLine() {return disLine != null;}private void paintFallingBlock(Graphics g) {if (fallingBlock != null) {g.setColor(FALLING_BLOCK_COLOR);fallingBlock.draw(g, gridSize);}}private void paintData(Graphics g) {int allCount = data.length * data[0].length;int rowCount = data[0].length;for (int i = 0; i < data.length; i++) {int x = 0;int y = i * gridSize;for (int j = 0; j < rowCount; j++) {x = j * gridSize;if (data[i][j] == DATA_EMPTY) {fill3DBlock(g, x, y,calBgColor(50, i, j, rowCount, allCount));} else {// fill3DBlock(g, x, y, calBlockColor(255, i, data.length));if (gameOverIndex <= i && gameOver)fill3DBlock(g, x, y, Color.GRAY);elsefill3DBlock(g, x, y,calBgColor(250, i, j, rowCount, allCount));}}}}private void fill3DBlock(Graphics g, int x, int y, Color color) {Graphics2D g2d = (Graphics2D) g.create();g2d.setColor(color);g2d.fill3DRect(x, y, gridSize, gridSize, true);g2d.dispose();}private Color calBlockColor(int base, int lineNum, int lineCount) {int r = base * lineNum / lineCount;return new Color(r, 0, (base - r) / 3);}private Color calBgColor(int base, int ci, int cj, int rowCount,int allCount) {int b = base * (ci * rowCount + cj) / allCount;int r = base * cj / rowCount;return new Color(r, base - b, b);}@Overridepublic void keyTyped(KeyEvent e) {}private void switchPause() {pause = !pause;}@Overridepublic void keyPressed(KeyEvent e) {switch (e.getKeyCode()) {case KeyEvent.VK_DOWN:case KeyEvent.VK_SPACE:case KeyEvent.VK_S:if (isCanControl() && isCanDown(fallingBlock))fallingBlock.down();break;case KeyEvent.VK_UP:case KeyEvent.VK_W:if (isCanControl() && isCanRotate(fallingBlock))fallingBlock.rotate();break;case KeyEvent.VK_LEFT:case KeyEvent.VK_A:if (isCanControl() && isCanLeft(fallingBlock)) {fallingBlock.left();}break;case KeyEvent.VK_RIGHT:case KeyEvent.VK_D:if (isCanControl() && isCanRight(fallingBlock)) {fallingBlock.right();}break;case KeyEvent.VK_P:case KeyEvent.VK_ENTER:switchPause();break;case KeyEvent.VK_ESCAPE:restart();default:break;}}private boolean isCanControl() {return !pause && !gameOver;}@Overridepublic void keyReleased(KeyEvent e) {}private boolean isValid(TetrisBlock newBlock) {int[][] shap = newBlock.getShap();int x = newBlock.getXInWorld();int y = newBlock.getYInWorld();for (int i = 0; i < shap.length; i++) {for (int j = 0; j < shap[i].length; j++) {if ((shap[i][j] == 1 && (i + y >= data.length || // 有某一格超过了下边界j + x < 0 || // 有某一格超过了左边界j + x >= data[i].length // 有某一格超过了右边界)))return false;if ((i + y < data.length && j + x >= 0 && j + x < data[i].length)&& (shap[i][j] & data[i + y][j + x]) == DATA_BLOCK) // 和其它重叠return false;}}return true;}private boolean isCanRotate(TetrisBlock block) {return isValid(block.copyRotate());}private boolean isCanDown(TetrisBlock block) {return isValid(block.copyDown());}private boolean isCanRight(TetrisBlock block) {return isValid(block.copyRight());}private boolean isCanLeft(TetrisBlock block) {return isValid(block.copyLeft());}}


ScorePanel.java

package com.ubird.tetris.ui;import java.awt.Color;import java.awt.Dimension;import java.awt.Font;import java.awt.FontMetrics;import java.awt.Graphics;import java.awt.Image;import java.io.IOException;import javax.imageio.ImageIO;import javax.swing.JPanel;import com.ubird.demo.TetrisDemo;import com.ubird.tetris.block.TetrisBlock;import com.ubird.ui.DrawUtil;public class ScorePanel extends JPanel{private static final long serialVersionUID = 1251179527381938354L;private Image bg = null;private Color fontColor= new Color(255,200,200);String[] tips = {" [A] 或者 [LEFT] : 向左移动", "[S] 或者 [DOWN] : 向下移动", "[D] 或者 [RIGHT] : 向右移动", " [W] 或者 [UP] : 旋转", "[P] 或者 [ENTER] : 暂停", " [ESC] : 重启"};private Color tipShadowColor = new Color(0,150,255);private TetrisBlock nextBlock;private int score;private final static int[] ALTER_SCORES = {10, 20, 40, 80};private Color versionAndAuthorColor = new Color(255,255,255);private int currDrawScore;private int lightIndex = 0;private int[][] lightPoses = {{137, 285, 236, 346},{137, 285, 236, 346},{137, 285, 236, 346},{137, 285, 236, 346},{137, 285, 236, 346},{137,346,236,407},{137,346,236,407},{137,346,236,407},{137,346,236,407},{137,346,236,407},{137,346,236,407},{137,407,236,468},{137,407,236,468},{137,407,236,468},{137,407,236,468},{137,346,236,407},{137,346,236,407},{137,346,236,407},{137,346,236,407},{137,346,236,407},{137,346,236,407},{137, 285, 236, 346},{137, 285, 236, 346},{137, 285, 236, 346},{137, 285, 236, 346},{137, 285, 236, 346}};private static Image TETRIS_RES = null;static{try {TETRIS_RES = ImageIO.read(ScorePanel.class.getClassLoader().getResource("com/ubird/ui/res/Tetris.png"));} catch (IOException e) {e.printStackTrace();}}public ScorePanel(int width, int height){this.setPreferredSize(new Dimension(width, height));try {bg = ImageIO.read(getClass().getClassLoader().getResource("com/ubird/ui/res/bg.png"));} catch (IOException e) {e.printStackTrace();}}public void addScore(int[] disLine) {if(disLine!=null && disLine.length>0){this.score += ALTER_SCORES[Math.min(disLine.length, ALTER_SCORES.length)-1];if(this.score>getMaxScore())clearScore();}}public void clearScore(){this.score = 0;this.currDrawScore = 0;}@Overrideprotected void paintComponent(Graphics g) {super.paintComponent(g);paintBg(g);paintSplit(g);paintVersionAndAuthor(g);paintScore(g);//paintNextBlock(g);paintTips(g, 10, 530);}private void paintVersionAndAuthor(Graphics g) {DrawUtil.drawShadowString(g, TetrisDemo.VERSION+" by "+TetrisDemo.AUTHOR, 12, 78, 90, 3, versionAndAuthorColor, fontColor);}private void paintSplit(Graphics g) {g.draw3DRect(0, 0, 2, getHeight(), true);}private void paintBg(Graphics g) {if(bg!=null){g.drawImage(bg, 0, 0, getWidth(), getHeight(), null);}else{g.setColor(Color.BLACK);g.fillRect(0, 0, (int)getWidth(), (int)getHeight());}}private void paintScore(Graphics g) {int width  = 338-255;int height = 463-205;int dx = (getWidth()- width)/2;g.drawImage(TETRIS_RES, dx, 250, dx+width, 250+height, 255, 205, 338, 463, null);g.drawImage(TETRIS_RES, dx, (int) (250+height*(1-currDrawScore/getMaxScore())), dx+width, 250+height, 350, (int)(463-height*currDrawScore/getMaxScore()), 432, 463, null);int lsx1 = lightPoses[lightIndex][0];int lsy1 = lightPoses[lightIndex][1];int lsx2 = lightPoses[lightIndex][2];int lsy2 = lightPoses[lightIndex][3];int ldw = lsx2-lsx1;int ldh = lsy2-lsy1;int ldx1 = dx + (width-ldw)/2;int scoreYFix = 0;if(score>0 & score<getMaxScore())scoreYFix = 10;else if(score >= getMaxScore())scoreYFix = 30;int ldy1 = (int) (463-height*currDrawScore/getMaxScore()+scoreYFix);int ldx2 = ldx1 + ldw;int ldy2 = ldy1+ldh;g.drawImage(TETRIS_RES, ldx1, ldy1, ldx2, ldy2, lsx1, lsy1, lsx2, lsy2, null);if(currDrawScore < score)currDrawScore += (score-currDrawScore)/2;currDrawScore = Math.min(currDrawScore, score);lightIndex = (lightIndex+1)%lightPoses.length;}private float getMaxScore() {return 200;}private void paintNextBlock(Graphics g) {if(nextBlock!=null){nextBlock.draw(g, 20);}}public void setNextBlock(TetrisBlock nextBlock) {this.nextBlock = nextBlock.copy();this.nextBlock.init( (int) (getPreferredSize().getWidth()/70), 8);}public void paintTips(Graphics g, int x, int y){int fontSize = 12;int fontHeight = 18;int shadowWidth = 3;int maxWidth = 0;Font f = new Font("宋体", Font.PLAIN, fontSize);FontMetrics fontMetrics = g.getFontMetrics(f);for(int i=0; i<tips.length; i++){String[] split = tips[i].split(":");int newWidth = (int) fontMetrics.getStringBounds(split[0], g).getWidth();maxWidth = maxWidth >  newWidth ? maxWidth : newWidth;}for(int i=0; i<tips.length; i++){String[] split = tips[i].split(":");int newWidth = (int) fontMetrics.getStringBounds(split[0], g).getWidth();maxWidth = maxWidth >  newWidth ? maxWidth : newWidth;DrawUtil.drawShadowString(g,  f, split[0], x+maxWidth-newWidth, y+i*fontHeight, shadowWidth, tipShadowColor, fontColor);DrawUtil.drawShadowString(g,  f, split[1], x+maxWidth, y+i*fontHeight, shadowWidth, tipShadowColor, fontColor);}}}



RepeatingReleasedEventsFixer.java

package com.ubird.ui.event;import java.awt.AWTEvent;import java.awt.Component;import java.awt.EventQueue;import java.awt.Toolkit;import java.awt.event.AWTEventListener;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.KeyEvent;import java.util.HashMap;import java.util.Map;import javax.swing.Timer;public class RepeatingReleasedEventsFixer implements AWTEventListener {    private final Map _map = new HashMap();    public void install() {        Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);    }    public void remove() {        Toolkit.getDefaultToolkit().removeAWTEventListener(this);    }    @Override    public void eventDispatched(AWTEvent event) {        assert event instanceof KeyEvent : "Shall only listen to KeyEvents, so no other events shall come here";        assert assertEDT(); // REMEMBER THAT THIS IS SINGLE THREADED, so no need for synch.        // ?: Is this one of our synthetic RELEASED events?        if (event instanceof Reposted) {            // -> Yes, so we shalln't process it again.            return;        }        // ?: KEY_TYPED event? (We're only interested in KEY_PRESSED and KEY_RELEASED).        if (event.getID() == KeyEvent.KEY_TYPED) {            // -> Yes, TYPED, don't process.            return;        }        final KeyEvent keyEvent = (KeyEvent) event;        // ?: Is this already consumed?        // (Note how events are passed on to all AWTEventListeners even though a previous one consumed it)        if (keyEvent.isConsumed()) {            return;        }        // ?: Is this RELEASED? (the problem we're trying to fix!)        if (keyEvent.getID() == KeyEvent.KEY_RELEASED) {            // -> Yes, so stick in wait            /*             * Really just wait until "immediately", as the point is that the subsequent PRESSED shall already have been             * posted on the event queue, and shall thus be the direct next event no matter which events are posted             * afterwards. The code with the ReleasedAction handles if the Timer thread actually fires the action due to             * lags, by cancelling the action itself upon the PRESSED.             */            final Timer timer = new Timer(2, null);            ReleasedAction action = new ReleasedAction(keyEvent, timer);            timer.addActionListener(action);            timer.start();            _map.put(Integer.valueOf(keyEvent.getKeyCode()), action);            // Consume the original            keyEvent.consume();        }        else if (keyEvent.getID() == KeyEvent.KEY_PRESSED) {            // Remember that this is single threaded (EDT), so we can't have races.            ReleasedAction action = (ReleasedAction) _map.remove(Integer.valueOf(keyEvent.getKeyCode()));            // ?: Do we have a corresponding RELEASED waiting?            if (action != null) {                // -> Yes, so dump it                action.cancel();            }            // System.out.println("PRESSED: [" + keyEvent + "]");        }        else {            throw new AssertionError("All IDs should be covered.");        }    }    /**     * The ActionListener that posts the RELEASED {@link RepostedKeyEvent} if the {@link Timer} times out (and hence the     * repeat-action was over).     */    private class ReleasedAction implements ActionListener {        private final KeyEvent _originalKeyEvent;        private Timer _timer;        ReleasedAction(KeyEvent originalReleased, Timer timer) {            _timer = timer;            _originalKeyEvent = originalReleased;        }        void cancel() {            assert assertEDT();            _timer.stop();            _timer = null;            _map.remove(Integer.valueOf(_originalKeyEvent.getKeyCode()));        }        @Override        public void actionPerformed(@SuppressWarnings ("unused") ActionEvent e) {            assert assertEDT();            // ?: Are we already cancelled?            // (Judging by Timer and TimerQueue code, we can theoretically be raced to be posted onto EDT by TimerQueue,            // due to some lag, unfair scheduling)            if (_timer == null) {                // -> Yes, so don't post the new RELEASED event.                return;            }            // Stop Timer and clean.            cancel();            // Creating new KeyEvent (we've consumed the original).            KeyEvent newEvent = new RepostedKeyEvent((Component) _originalKeyEvent.getSource(),                    _originalKeyEvent.getID(), _originalKeyEvent.getWhen(), _originalKeyEvent.getModifiers(),                    _originalKeyEvent.getKeyCode(), _originalKeyEvent.getKeyChar(), _originalKeyEvent.getKeyLocation());            // Posting to EventQueue.            Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(newEvent);            // System.out.println("Posted synthetic RELEASED [" + newEvent + "].");        }    }    /**     * Marker interface that denotes that the {@link KeyEvent} in question is reposted from some     * {@link AWTEventListener}, including this. It denotes that the event shall not be "hack processed" by this class     * again. (The problem is that it is not possible to state "inject this event from this point in the pipeline" - one     * have to inject it to the event queue directly, thus it will come through this {@link AWTEventListener} too.     */    public interface Reposted {        // marker    }    /**     * Dead simple extension of {@link KeyEvent} that implements {@link Reposted}.     */    public static class RepostedKeyEvent extends KeyEvent implements Reposted {        public RepostedKeyEvent(@SuppressWarnings ("hiding") Component source, @SuppressWarnings ("hiding") int id,                long when, int modifiers, int keyCode, char keyChar, int keyLocation) {            super(source, id, when, modifiers, keyCode, keyChar, keyLocation);        }    }    private static boolean assertEDT() {        if (!EventQueue.isDispatchThread()) {            throw new AssertionError("Not EDT, but [" + Thread.currentThread() + "].");        }        return true;    }}

DrawUtil.java

package com.ubird.ui;import java.awt.AlphaComposite;import java.awt.Color;import java.awt.Font;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.RenderingHints;public class DrawUtil {/** * 绘制外发光文字 *  * @param g * @param text *            要绘制的文本 * @param fontSize *            字体大小 * @param x *            绘制位置 * @param y *            绘制位置 * @param shadowWidth *            阴影宽带 * @param shadowColor *            阴影颜色 * @param fontColor *            字体颜色 */public static void drawShadowString(Graphics g, String text, int fontSize,int x, int y, int shadowWidth, Color shadowColor, Color fontColor) {Font font = new Font("Monospace", Font.PLAIN, fontSize);drawShadowString(g, font, text, x, y, shadowWidth, shadowColor,fontColor);}/** * 绘制外发光文字 *  * @param g * @param font *            字体 * @param text *            要绘制的文本 * @param fontSize *            字体大小 * @param x *            绘制位置 * @param y *            绘制位置 * @param shadowWidth *            阴影宽带 * @param shadowColor *            阴影颜色 * @param fontColor *            字体颜色 */public static void drawShadowString(Graphics g, Font font, String text,int x, int y, int shadowWidth, Color shadowColor, Color fontColor) {g.setFont(font);g.setColor(shadowColor);Graphics2D g2d = (Graphics2D) g;g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);for (int i = 1; i <= shadowWidth; i++) {g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f + 0.2f * (shadowWidth - i)/ shadowWidth));for (int j = 0; j < 8; j++) {int offsetX = j % 3 - 1;int offsetY = j / 3 - 1;g.drawString(text, x + offsetX * i, y + offsetY * i);}}g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));g.setColor(fontColor);g.drawString(text, x, y);}}


UFrame.java

package com.ubird.ui;import javax.swing.JFrame;public class UFrame extends JFrame {private static final long serialVersionUID = -1016305161292011740L;public UFrame() {super(" Algorithm by JohnCha");}public UFrame(String title) {super(title);}}


bg.png


Tetris.png