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;
}
}
}
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