炫彩的俄罗斯方块



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() {
			@Override
			public float calX(float vx0, float ax, int time) {
				return vx0 + ax * time / 80f;
			}

			@Override
			public 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;
		} else
			return 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);
					else
						fill3DBlock(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);
	}

	@Override
	public void keyTyped(KeyEvent e) {
	}

	private void switchPause() {
		pause = !pause;
	}

	@Override
	public 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;
	}

	@Override
	public 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;
	}
	
	@Override
	protected 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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值