俄罗斯方块游戏是一个上手简单,老少皆宜的游戏,它的基本规则是移动、旋转和摆放游戏自动产生的各种方块,使之排列成完整的一行或多行并且消除得分。
一、你能学到什么?
通过本文的阅读,读者可以对Swing版俄罗斯方块游戏的本身,对游戏中的关键点,如图形变换、键盘事件处理、游戏进度保存、满行和消行等都会有较好的理解。
接下来我们就来看看。如何使用Swing完成一个俄罗斯方块的游戏。游戏界面有四个部分组成:
一般来讲,一个图形有四个点,可以表示出常用的“一字型”,“T字型”,“Z字型”以及“L字型”方块。
如果将四个点的一个或者多个重叠,或者不采用常用的“一字型”,“T字型”,“Z字型”以及“L字型”方块,那么可以演变出更多的图形出来。如果想要更加丰富和复杂的图形,可以使用更多的点去表示想要的图形。
四个点组成的图形有如下几种:
2.2 非重合的常规图形
如果将四个点中的一个或者多个点重合,就可以有如下几种类型的图形:
2.3 无重合点的不常规图
如果四个点不重合,还可以有如下几种比较诡异的图形,可以为游戏增加难度。
2.4 图形对象类表示
格子类( Grid.java ) ,俄罗斯方块游戏中的图形由四个格子组成。每个格子类有下x , y两个坐标位置, 颜色,以及格子图形绘制方法等。
如:
package my.games.russia.model;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import my.games.russia.constants.RussiaGameConstant;
/**
* 格子,俄罗斯方块游戏中的图形由四个格子组成
* @author wangmengjun
*
*/
public class Grid implements Serializable {
private static final long serialVersionUID = -3722886719784770741L;
/**x位置*/
private int x;
/**y位置*/
private int y;
/**格子的颜色*/
private Color color;
public Grid() {
}
public Grid(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void draw(Graphics2D g2) {
//TODO:
}
}
图形抽象类 ( AbstractRussiaSquare .java )。抽象类包含一些公用的属性(如:每个图形由四个方块组成)、公用的方法(如向左移动、向右移动)、抽象方法(如图形变换需要子类实现细节)。
如:
package my.games.russia.model;
import java.awt.Color;
import java.awt.Graphics2D;
import java.io.Serializable;
import my.games.russia.constants.RussiaGameConstant;
/**
* 俄罗斯方块游戏,图形的抽象类
* @author wangmengjun
*
*/
public abstract class AbstractRussiaSquare implements Serializable {
private static final long serialVersionUID = 192398482620404584L;
/**每一个图形都是有四个小方块Grid组成*/
protected Grid[] grids = { null, null, null, null };
/**xLocations*/
protected int[] xLocations = { 0, 0, 0, 0 };
/**yLocations*/
protected int[] yLocations = { 0, 0, 0, 0 };
/**图形是否alive,即是否还能变换*/
protected boolean alive;
/**图形格子的颜色*/
protected Color color;
/**图形初始状态,图形转换的状态*/
public int state;
public AbstractRussiaSquare() {
int r = (int) (Math.random() * 256);
int g = (int) (Math.random() * 256);
int b = (int) (Math.random() * 256);
this.color = new Color(r, g, b);
grids[0] = new Grid(0, 0, color);
grids[1] = new Grid(0, 0, color);
grids[2] = new Grid(0, 0, color);
grids[3] = new Grid(0, 0, color);
alive = true;
}
/**
* 图形绘制
*/
public void draw(Graphics2D g2) {
for(Grid grid : grids) {
grid.draw(g2);
}
}
/**
* 往左移动
*
* @param flag
*/
protected void moveLeft(int[][] flags) {
if (!alive) {
return;
}
//TODO:坐标变化
}
/**
* 往右移动
*
* @param flag
*/
protected void moveLeft(int[][] flags) {
if (!alive) {
return;
}
//TODO:坐标变化
}
/**
* 往下移动
*
* @param flag
*/
protected void moveDown(int[][] flags) {
if (!alive) {
return;
}
//TODO:坐标变化
}
protected void isStateChangeAllowed(int[][] flags, int state) {
//TODO
}
/**
/**
* 每个图形的图形变化各不相同,需要子类实现细节
*/
protected abstract void changeState(int[][] flags);
}
每个子类,只要继承了该抽象父类即可拥有公有的方法和属性,实现父类定义的抽象方法就可以拥有自身的实现细节。
图形变换或者图形旋转是俄罗斯方块游戏中比较关键的一个部分。 图形变换的思想?
因为一个图形有四个点来表示,可以先确定其中的一个点的变换位置,然后其它的三个点根据这个确定的基点进行位置调整就可以了。
比如,以一字型的图形为例,只要第二号格子为固定点,相继算出其它格子的位置即可。
package my.games.russia.model;
import java.util.Random;
/**
* 一字型
* @author wangmengjun
*
*/
public class RussiaSquare_2 extends AbstractRussiaSquare {
private static final long serialVersionUID = -4746450405460864752L;
public RussiaSquare_2() {
initGrids();
}
/* (non-Javadoc)
* @see my.games.russia.model.AbstractRussiaSquare#changeState(int[][])
*/
@Override
public void changeState(int[][] flags) {
int fixedXLocation = grids[1].getX();
int fixedYLocation = grids[1].getY();
switch (state) {
case 0:
/**
* 横向到竖直方向转换
*/
/**
* 第二个点保持不变
*/
xLocations[1] = fixedXLocation;
yLocations[1] = fixedYLocation;
xLocations[0] = fixedXLocation;
yLocations[0] = fixedYLocation - 1;
xLocations[2] = fixedXLocation;
yLocations[2] = fixedYLocation + 1;
xLocations[3] = fixedXLocation;
yLocations[3] = fixedYLocation + 2;
isAllowChangeState(flags, 2);
break;
case 1:
xLocations[1] = fixedXLocation;
yLocations[1] = fixedYLocation;
/**
* 竖直到横向转换
*/
xLocations[0] = fixedXLocation - 1;
yLocations[0] = fixedYLocation;
xLocations[2] = fixedXLocation + 1;
yLocations[2] = fixedYLocation;
xLocations[3] = fixedXLocation + 2;
yLocations[3] = fixedYLocation;
isAllowChangeState(flags, 2);
break;
default:
break;
}
}
/* (non-Javadoc)
* @see my.games.russia.model.AbstractRussiaSquare#initGrids()
*/
@Override
public void initGrids() {
state = new Random().nextInt(2);
switch (state) {
case 0:
/**
* 竖直
*/
grids[0].setX(9);
grids[0].setY(0);
grids[1].setX(grids[0].getX());
grids[1].setY(grids[0].getY() + 1);
grids[2].setX(grids[0].getX());
grids[2].setY(grids[0].getY() + 2);
grids[3].setX(grids[0].getX());
grids[3].setY(grids[0].getY() + 3);
break;
case 1:
/**
* 横向
*/
grids[0].setX(8);
grids[0].setY(0);
grids[1].setX(grids[0].getX() + 1);
grids[1].setY(grids[0].getY());
grids[2].setX(grids[0].getX() + 2);
grids[2].setY(grids[0].getY());
grids[3].setX(grids[0].getX() + 3);
grids[3].setY(grids[0].getY());
break;
default:
break;
}
}
}
采用类似的方法,可以写出T字形的方块转换:
package my.games.russia.model;
import java.util.Random;
/**
* T字型方块
* @author wangmengjun
*
*/
public class RussiaSquare_3 extends AbstractRussiaSquare {
private static final long serialVersionUID = -8336206016924545562L;
public RussiaSquare_3() {
initGrids();
}
/* (non-Javadoc)
* @see my.games.russia.model.AbstractRussiaSquare#changeState(int[][])
*/
@Override
public void changeState(int[][] flags) {
switch (state) {
case 0:
xLocations[0] = grids[0].getX();
yLocations[0] = grids[0].getY();
xLocations[1] = xLocations[0];
yLocations[1] = yLocations[0] + 1;
xLocations[2] = xLocations[0] + 1;
yLocations[2] = yLocations[0] + 1;
xLocations[3] = xLocations[0];
yLocations[3] = yLocations[0] + 2;
isAllowChangeState(flags, 4);
break;
case 1:
xLocations[0] = grids[0].getX() - 1;
yLocations[0] = grids[0].getY() + 1;
xLocations[1] = xLocations[0] + 1;
yLocations[1] = yLocations[0];
xLocations[2] = xLocations[0] + 2;
yLocations[2] = yLocations[0];
xLocations[3] = xLocations[0] + 1;
yLocations[3] = yLocations[0] + 1;
isAllowChangeState(flags, 4);
break;
case 2:
xLocations[0] = grids[0].getX() + 1;
yLocations[0] = grids[0].getY() - 1;
xLocations[1] = xLocations[0] - 1;
yLocations[1] = yLocations[0] + 1;
xLocations[2] = xLocations[0];
yLocations[2] = yLocations[0] + 1;
xLocations[3] = xLocations[0];
yLocations[3] = yLocations[0] + 2;
isAllowChangeState(flags, 4);
break;
case 3:
xLocations[0] = grids[0].getX();
yLocations[0] = grids[0].getY();
xLocations[1] = xLocations[0] - 1;
yLocations[1] = yLocations[0] + 1;
xLocations[2] = xLocations[0];
yLocations[2] = yLocations[0] + 1;
xLocations[3] = xLocations[0] + 1;
yLocations[3] = yLocations[0] + 1;
isAllowChangeState(flags, 4);
break;
default:
break;
}
}
/* (non-Javadoc)
* @see my.games.russia.model.AbstractRussiaSquare#initGrids()
*/
@Override
public void initGrids() {
state = new Random().nextInt(4);
switch (state) {
case 0:
grids[0].setX(9);
grids[0].setY(0);
grids[1].setX(grids[0].getX() - 1);
grids[1].setY(grids[0].getY() + 1);
grids[2].setX(grids[0].getX());
grids[2].setY(grids[0].getY() + 1);
grids[3].setX(grids[0].getX() + 1);
grids[3].setY(grids[0].getY() + 1);
break;
case 1:
grids[0].setX(9);
grids[0].setY(0);
grids[1].setX(grids[0].getX());
grids[1].setY(grids[0].getY() + 1);
grids[2].setX(grids[0].getX() + 1);
grids[2].setY(grids[0].getY() + 1);
grids[3].setX(grids[0].getX());
grids[3].setY(grids[0].getY() + 2);
break;
case 2:
grids[0].setX(9);
grids[0].setY(0);
grids[1].setX(grids[0].getX() + 1);
grids[1].setY(grids[0].getY());
grids[2].setX(grids[0].getX() + 2);
grids[2].setY(grids[0].getY());
grids[3].setX(grids[0].getX() + 1);
grids[3].setY(grids[0].getY() + 1);
break;
case 3:
grids[0].setX(9);
grids[0].setY(0);
grids[1].setX(grids[0].getX() - 1);
grids[1].setY(grids[0].getY() + 1);
grids[2].setX(grids[0].getX());
grids[2].setY(grids[0].getY() + 1);
grids[3].setX(grids[0].getX());
grids[3].setY(grids[0].getY() + 2);
break;
default:
break;
}
}
}
键盘事件主要有如下几个部分
向左
/**
* 往左移动
*
*/
public void moveLeft(int[][] flags) {
if (!alive) {
return;
}
/**
* 将现有的点赋值到xLocation和yLocation上去
*/
for (int i = 0; i < grids.length; i++) {
xLocations[i] = grids[i].getX() - 1;
yLocations[i] = grids[i].getY();
}
if (xLocations[0] >= RussiaGameConstant.LEFT && flags[xLocations[0]][yLocations[0]] == 0
&& xLocations[1] >= RussiaGameConstant.LEFT
&& flags[xLocations[1]][yLocations[1]] == 0
&& xLocations[2] >= RussiaGameConstant.LEFT
&& flags[xLocations[2]][yLocations[2]] == 0
&& xLocations[3] >= RussiaGameConstant.LEFT
&& flags[xLocations[3]][yLocations[3]] == 0) {
for (int i = 0; i < grids.length; i++) {
grids[i].setX(xLocations[i]);
}
}
}
向右以及向下都和向左操作类似。
向右
/**
* 往右移动
*/
public void moveRight(int flags[][]) {
if (!alive) {
return;
}
for (int i = 0; i < grids.length; i++) {
xLocations[i] = grids[i].getX() + 1;
yLocations[i] = grids[i].getY();
}
if (xLocations[0] <= RussiaGameConstant.RIGHT && flags[xLocations[0]][yLocations[0]] == 0
&& xLocations[1] <= RussiaGameConstant.RIGHT
&& flags[xLocations[1]][yLocations[1]] == 0
&& xLocations[2] <= RussiaGameConstant.RIGHT
&& flags[xLocations[2]][yLocations[2]] == 0
&& xLocations[3] <= RussiaGameConstant.RIGHT
&& flags[xLocations[3]][yLocations[3]] == 0) {
for (int i = 0; i < grids.length; i++) {
grids[i].setX(xLocations[i]);
}
}
}
向下
/**
* 往下移动
*/
public void moveDown(int[][] flags) {
for (int i = 0; i < grids.length; i++) {
xLocations[i] = grids[i].getX();
yLocations[i] = grids[i].getY() + 1;
}
if (yLocations[0] <= RussiaGameConstant.DOWN && flags[xLocations[0]][yLocations[0]] == 0
&& yLocations[1] <= RussiaGameConstant.DOWN
&& flags[xLocations[1]][yLocations[1]] == 0
&& yLocations[2] <= RussiaGameConstant.DOWN
&& flags[xLocations[2]][yLocations[2]] == 0
&& yLocations[3] <= RussiaGameConstant.DOWN
&& flags[xLocations[3]][yLocations[3]] == 0) {
for (int i = 0; i < grids.length; i++) {
grids[i].setY(yLocations[i]);
;
}
} else {
alive = false;
}
}
变换图形
每个图形,或有一种变换,或者有2种或者有四种变换,可以根据图形的特性进行处理。 比如,将“T字型”图型按照顺时针旋转,拥有四种变换,创建对象的时候已经确定了state是多少(state确定初始的位置是哪里),此后的变换只要对state加1并对4求余就可以知道怎么变换。
/* (non-Javadoc)
* @see my.games.russia.model.AbstractRussiaSquare#changeState(int[][])
*/
@Override
public void changeState(int[][] flags) {
switch (state) {
case 0:
xLocations[0] = grids[0].getX();
yLocations[0] = grids[0].getY();
xLocations[1] = xLocations[0];
yLocations[1] = yLocations[0] + 1;
xLocations[2] = xLocations[0] + 1;
yLocations[2] = yLocations[0] + 1;
xLocations[3] = xLocations[0];
yLocations[3] = yLocations[0] + 2;
isAllowChangeState(flags, 4);
break;
case 1:
xLocations[0] = grids[0].getX() - 1;
yLocations[0] = grids[0].getY() + 1;
xLocations[1] = xLocations[0] + 1;
yLocations[1] = yLocations[0];
xLocations[2] = xLocations[0] + 2;
yLocations[2] = yLocations[0];
xLocations[3] = xLocations[0] + 1;
yLocations[3] = yLocations[0] + 1;
isAllowChangeState(flags, 4);
break;
case 2:
xLocations[0] = grids[0].getX() + 1;
yLocations[0] = grids[0].getY() - 1;
xLocations[1] = xLocations[0] - 1;
yLocations[1] = yLocations[0] + 1;
xLocations[2] = xLocations[0];
yLocations[2] = yLocations[0] + 1;
xLocations[3] = xLocations[0];
yLocations[3] = yLocations[0] + 2;
isAllowChangeState(flags, 4);
break;
case 3:
xLocations[0] = grids[0].getX();
yLocations[0] = grids[0].getY();
xLocations[1] = xLocations[0] - 1;
yLocations[1] = yLocations[0] + 1;
xLocations[2] = xLocations[0];
yLocations[2] = yLocations[0] + 1;
xLocations[3] = xLocations[0] + 1;
yLocations[3] = yLocations[0] + 1;
isAllowChangeState(flags, 4);
break;
default:
break;
}
}
一键到底
按下空格键一直向下的操作,其实就是在可以动的范围下,一直调用moveDown()方法
case KeyEvent.VK_SPACE:
while (sr1.isAlive()) {
sr1.moveDown(flag);
}
针对键盘的操作,大家可以定义一个内部的事件处理器。
private class KeyHandler implements KeyListener {
public void keyPressed(KeyEvent event) {
if (!gameState.isRunState()) {
return;
}
int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.VK_LEFT:
sr1.moveLeft(flag);
break;
case KeyEvent.VK_RIGHT:
sr1.moveRight(flag);
break;
case KeyEvent.VK_UP:
sr1.changeState(flag);
break;
case KeyEvent.VK_DOWN:
sr1.moveDown(flag);
break;
case KeyEvent.VK_SPACE:
while (sr1.isAlive()) {
sr1.moveDown(flag);
}
default:
break;
}
repaint();
}
public void keyReleased(KeyEvent event) {
}
public void keyTyped(KeyEvent event) {
}
}
然后,在游戏Panel创建的时候,添加上去即可。
addKeyListener(new KeyHandler());
用一个二维数组记录当前屏幕上的方块状态,0表示没有方块,1表示有方块。
满行条件?
满行的判断就归结到某一行1的个数是否等于该行列的总数,如果是就满足满行条件。
当有满行情况出现的时候,需要进行消除和计分操作。
如何消行?
消除行的一个做法就是将该行以上的行通通往下移,移动之后在将第一行的flag全部置为0。
or (int i = RussiaGameConstant.UP; i <= RussiaGameConstant.DOWN; i++) {
int count = 0;
for (int j = RussiaGameConstant.LEFT; j <= RussiaGameConstant.RIGHT; j++) {
count += flag[j][i];
}
/*
* flag[i][j] =1 表示这个位置有小方块,如果一行的位置都有小方块,那么满行的个数num加1.
* 并且消除行。
*/
if (count == RussiaGameConstant.GRID_COLUMN_NUMBER) {
num++;
/**
* 消除行操作。
*/
for (int m = i; m > RussiaGameConstant.UP; m--) {
for (int n = RussiaGameConstant.LEFT; n <= RussiaGameConstant.RIGHT; n++) {
flag[n][m] = flag[n][m - 1];
color[n][m] = color[n][m - 1];
}
}
/*
* 重新将第一行的flag[s][0]置为0
*/
for (int s = RussiaGameConstant.LEFT; s <= RussiaGameConstant.RIGHT; s++) {
flag[s][RussiaGameConstant.UP] = 0;
}
}
}
/**
* @param num
* 方块满行的个数
*/
private void calculateScore(int num) {
switch (num) {
case 1:
score += 10;
break;
case 2:
score += 20;
break;
case 3:
score += 50;
break;
case 4:
score += 100;
break;
default:
break;
}
}
俄罗斯方块游戏结束的判断其实很简单,只要判断第一行的标记位是否有1即可。
/**
* 如果是top touched 则执行Game Over的相关操作
*
*/
private void judgeGameOver() {
if (isTopTouched()) {
gameState = GameState.OVER;
writeScore();
int result = JOptionPane.showConfirmDialog(frame, "Game over! Continue?", "俄罗斯方块",
JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
for (int i = RussiaGameConstant.LEFT; i <= RussiaGameConstant.RIGHT; i++) {
for (int j = RussiaGameConstant.UP; j <= RussiaGameConstant.DOWN; j++) {
flag[i][j] = 0;
}
}
gameState = GameState.RUN;
score = 0;
timer.start();
} else {
System.exit(0);
}
}
}
private boolean isTopTouched() {
for (int i = RussiaGameConstant.LEFT; i <= RussiaGameConstant.RIGHT; i++) {
if (flag[i][RussiaGameConstant.UP] == 1) {
return true;
}
}
return false;
}
游戏进度的保存和加载功能是通过序列化和反序列化来完成的。
通过序列化的方式将当前游戏运行状态中用到的一些重要对象属性序列化到文件中加以保存,从而达到记录当前游戏状态的效果。
通过反序列化的方式将序列化后的对象读取出来,从而达到恢复之前游戏保存时的状态的效果。用户可以在此基础上继续进行游戏。
通过反序列化的方式将序列化后的对象读取出来,从而达到恢复之前游戏保存时的状态的效果。用户可以在此基础上继续进行游戏。
(一)编写保存游戏进度、加载游戏进度的事件监听器
private class LoadAction implements ActionListener {
public void actionPerformed(ActionEvent event) {
FileDialog dialog = new FileDialog(RussiaGameFrame.this, "Open",
FileDialog.LOAD);
dialog.setVisible(true);
String dir = dialog.getDirectory();
String fileName = dialog.getFile();
String filePath = dir + fileName;
if (fileName != null && fileName.trim().length() != 0) {
File file = new File(filePath);
panel.readSelfFromFile(file);
startMI.setEnabled(false);
pauseMI.setEnabled(true);
} else {
JOptionPane.showConfirmDialog(RussiaGameFrame.this,
"文件名为空\n装载游戏进度失败", "俄罗斯方块", JOptionPane.DEFAULT_OPTION);
}
}
}
private class SaveAction implements ActionListener {
public void actionPerformed(ActionEvent event) {
if (panel.gameState == GameState.INITIALIZE) {
JOptionPane
.showConfirmDialog(RussiaGameFrame.this,
"游戏没有运行\n不能保存游戏进度", "俄罗斯方块",
JOptionPane.DEFAULT_OPTION);
return;
}
FileDialog dialog = new FileDialog(RussiaGameFrame.this, "Save",
FileDialog.SAVE);
dialog.setVisible(true);
String dir = dialog.getDirectory();
String fileName = dialog.getFile();
String filePath = dir + fileName;
if (fileName != null && fileName.trim().length() != 0) {
File file = new File(filePath);
panel.writeSelfToFile(file);
} else {
JOptionPane.showConfirmDialog(RussiaGameFrame.this,
"文件名为空\n保存游戏进度失败", "俄罗斯方块", JOptionPane.DEFAULT_OPTION);
}
}
}
(二)添加用于保存和加载功能的MenuItem, 并为它们添加ActionListenser
private JMenuItem loadMI = new JMenuItem("Open");
public JMenuItem saveMI = new JMenuItem("Save");
setMenu.add(loadMI);
setMenu.add(saveMI);
loadMI.addActionListener(new LoadAction());
saveMI.addActionListener(new SaveAction());
(三)编写具体的业务逻辑
public void writeSelfToFile(File file) {
try {
FileOutputStream fileStream = new FileOutputStream(file);
ObjectOutputStream objectStream = new ObjectOutputStream(fileStream);
objectStream.writeObject(flag);
objectStream.writeObject(color);
objectStream.writeObject(sr1);
objectStream.writeObject(sr2);
objectStream.writeObject(new Integer(score));
objectStream.close();
fileStream.close();
JOptionPane.showConfirmDialog(frame, "保存游戏进度成功", "俄罗斯方块", JOptionPane.DEFAULT_OPTION);
} catch (Exception e) {
JOptionPane.showConfirmDialog(frame, e.toString() + "\n保存游戏进度失败", "俄罗斯方块",
JOptionPane.DEFAULT_OPTION);
}
}
public void readSelfFromFile(File file) {
try {
int[][] f;
AbstractRussiaSquare s1, s2;
Integer integer;
Color[][] c;
FileInputStream fileStream = new FileInputStream(file);
ObjectInputStream objectStream = new ObjectInputStream(fileStream);
f = (int[][]) objectStream.readObject();
c = (Color[][]) objectStream.readObject();
s1 = (AbstractRussiaSquare) objectStream.readObject();
s2 = (AbstractRussiaSquare) objectStream.readObject();
integer = (Integer) objectStream.readObject();
objectStream.close();
fileStream.close();
if (f != null && c != null && s1 != null && s2 != null && integer != null) {
flag = f;
color = c;
sr1 = s1;
sr2 = s2;
score = integer.intValue();
gameState = GameState.RUN;
frame.saveMI.setEnabled(true);
if (!timer.isRunning()) {
timer.start();
}
repaint();
JOptionPane.showConfirmDialog(frame, "装载游戏进度成功", "俄罗斯方块",
JOptionPane.DEFAULT_OPTION);
}
} catch (Exception e) {
JOptionPane.showConfirmDialog(frame, e.toString() + "\n装载游戏进度失败", "俄罗斯方块",
JOptionPane.DEFAULT_OPTION);
}
}
得分排行榜上列出Top 10的记录信息,包括玩家名称,得分和名次。
该功能可以通过如下几个步骤完成:
(一)创建游戏记录类和比较器
package my.games.russia.model;
import java.io.Serializable;
/**
* 记录玩家的信息
* @author wangmengjun
*
*/
public class Record implements Serializable {
private static final long serialVersionUID = 9143467974370981697L;
/**玩家姓名*/
private String player = null;
/**玩家得分*/
private int score = 0;
public Record(String player, int score) {
this.player = player;
this.score = score;
}
/**
* @return the player
*/
public String getPlayer() {
return player;
}
/**
* @param player the player to set
*/
public void setPlayer(String player) {
this.player = player;
}
/**
* @return the score
*/
public int getScore() {
return score;
}
/**
* @param score the score to set
*/
public void setScore(int score) {
this.score = score;
}
}
先通过分数比较,如果分数一致,则比较玩家名字。
package my.games.russia.compare;
import java.util.Comparator;
import my.games.russia.model.Record;
/**
* 俄罗斯记录的比较器
* @author wangmengjun
*
*/
public class RecordComparator implements Comparator<Record> {
public int compare(Record o1, Record o2) {
Record r1 = (Record) o1;
Record r2 = (Record) o2;
int compareScore = compareScore(r1, r2);
return (0 == compareScore) ? compareScore : compareName(r1, r2);
}
private int compareScore(Record r1, Record r2) {
return r2.getScore() - r1.getScore();
}
private int compareName(Record r1, Record r2) {
return r1.getPlayer().compareTo(r2.getPlayer());
}
}
(二)完成游戏结束后对记录文件更新的操作。
private void writeScore() {
if (score == 0) {
return;
}
File file = new File("file.dat");
RussiaGameRecords records = new ReadRecord().readRecordsFromFile(file);
if (records == null || records.isEmpty() || !records.isFull()
|| (records.getLastAvailableRecord().getScore() < score && records.isFull())) {
String playerName = JOptionPane.showInputDialog("Please input your name");
if (playerName == null || playerName.length() == 0) {
playerName = "无名英雄";
}
Record record = new Record(playerName, score);
records.addRecordToTopTen(record);
new WriteRecord().writeRecordToFile(records, file);
}
}
/**
* 如果是top touched 则执行Game Over的相关操作
*
*/
private void judgeGameOver() {
if (isTopTouched()) {
gameState = GameState.OVER;
writeScore();
int result = JOptionPane.showConfirmDialog(frame, "Game over! Continue?", "俄罗斯方块",
JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
for (int i = RussiaGameConstant.LEFT; i <= RussiaGameConstant.RIGHT; i++) {
for (int j = RussiaGameConstant.UP; j <= RussiaGameConstant.DOWN; j++) {
flag[i][j] = 0;
}
}
gameState = GameState.RUN;
score = 0;
timer.start();
} else {
System.exit(0);
}
}
}
(三)完成点击Record相关的MenuItem,读取记录信息,并用ScrollPane展示出来。
package my.games.russia.ui;
import java.io.File;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import my.games.russia.model.Record;
import my.games.russia.model.RussiaGameRecords;
/**
*
* @author wangmengjun
*
*/
public class ReadScrollPane {
public JScrollPane getReadScrollPane(RussiaGameRecords records, File recordFile) {
Object[][] data = new Object[records.getNumberInRecord()][3];
for (int i = 0; i < records.getNumberInRecord(); i++) {
Record record = records.getRecords()[i];
data[i][0] = String.valueOf(i + 1);
data[i][1] = record.getPlayer();
data[i][2] = String.valueOf(record.getScore());
}
Object[] columnNames = new Object[3];
columnNames[0] = "ID";
columnNames[1] = "Name";
columnNames[2] = "Score";
JTable table = new JTable(data, columnNames);
table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
JScrollPane pane = new JScrollPane(table);
return pane;
}
}
为了游戏更具随机性,随机产生方块主要包含两个部分的随机性。
编写一个工厂类,随机产生方块:如产生一字型的方块、T字形的方块等。
package my.games.russia.util;
import java.util.Random;
import my.games.russia.model.AbstractRussiaSquare;
import my.games.russia.model.RussiaSquare_1;
import my.games.russia.model.RussiaSquare_2;
import my.games.russia.model.RussiaSquare_3;
/**
*
* @author wangmengjun
*
*/
public class RussiaSquareFactory {
private static final int TOTAL_RUSSIA_SQUARE_COUNT = 3;
public static AbstractRussiaSquare generateNextRussiaSquareByRandom() {
AbstractRussiaSquare rs = null;
int index = new Random().nextInt(TOTAL_RUSSIA_SQUARE_COUNT);
switch (index) {
case 0:
rs = new RussiaSquare_1();
break;
case 1:
rs = new RussiaSquare_2();
break;
case 2:
rs = new RussiaSquare_3();
break;
default:
rs = new RussiaSquare_1();
break;
}
return rs;
}
}
每个图形通过旋转,都可以有不一样的初始化形态。比如T字形的方块就可以有四种形态,初始化的时候,也就有四种初始状态。如下是T字形方块的初始化:
/* (non-Javadoc)
* @see my.games.russia.model.AbstractRussiaSquare#initGrids()
*/
@Override
public void initGrids() {
state = new Random().nextInt(4);
switch (state) {
case 0:
grids[0].setX(9);
grids[0].setY(0);
grids[1].setX(grids[0].getX() - 1);
grids[1].setY(grids[0].getY() + 1);
grids[2].setX(grids[0].getX());
grids[2].setY(grids[0].getY() + 1);
grids[3].setX(grids[0].getX() + 1);
grids[3].setY(grids[0].getY() + 1);
break;
case 1:
grids[0].setX(9);
grids[0].setY(0);
grids[1].setX(grids[0].getX());
grids[1].setY(grids[0].getY() + 1);
grids[2].setX(grids[0].getX() + 1);
grids[2].setY(grids[0].getY() + 1);
grids[3].setX(grids[0].getX());
grids[3].setY(grids[0].getY() + 2);
break;
case 2:
grids[0].setX(9);
grids[0].setY(0);
grids[1].setX(grids[0].getX() + 1);
grids[1].setY(grids[0].getY());
grids[2].setX(grids[0].getX() + 2);
grids[2].setY(grids[0].getY());
grids[3].setX(grids[0].getX() + 1);
grids[3].setY(grids[0].getY() + 1);
break;
case 3:
grids[0].setX(9);
grids[0].setY(0);
grids[1].setX(grids[0].getX() - 1);
grids[1].setY(grids[0].getY() + 1);
grids[2].setX(grids[0].getX());
grids[2].setY(grids[0].getY() + 1);
grids[3].setX(grids[0].getX());
grids[3].setY(grids[0].getY() + 2);
break;
default:
break;
}
}
4.3 方块下降和速度改变
方块下降可以采用Timer来控制。如:
public Timer timer;
public TimerAction timerAction;
timerAction = new TimerAction();
timer = new Timer(1000, timerAction);
TimeAction类执行相关的逻辑行为,如:
private class TimerAction implements ActionListener, Serializable {
private static final long serialVersionUID = -6117702515382009989L;
public void actionPerformed(ActionEvent event) {
if (!gameState.isRunState()) {
return;
}
//满行的个数
int num = 0;
sr1.moveDown(flag);
if (!sr1.isAlive()) {
for (int i = 0; i < 4; i++) {
Grid[] grids = sr1.getGrids();
flag[grids[i].getX()][grids[i].getY()] = 1;
color[grids[i].getX()][grids[i].getY()] = sr1.getColor();
}
judgeGameOver();
for (int i = RussiaGameConstant.UP; i <= RussiaGameConstant.DOWN; i++) {
int count = 0;
for (int j = RussiaGameConstant.LEFT; j <= RussiaGameConstant.RIGHT; j++) {
count += flag[j][i];
}
/*
* flag[i][j] =1 表示这个位置有小方块,如果一行的位置都有小方块,那么满行的个数num加1.
* 并且消除行。
*/
if (count == RussiaGameConstant.GRID_COLUMN_NUMBER) {
num++;
/**
* 消除行操作。
*/
for (int m = i; m > RussiaGameConstant.UP; m--) {
for (int n = RussiaGameConstant.LEFT; n <= RussiaGameConstant.RIGHT; n++) {
flag[n][m] = flag[n][m - 1];
color[n][m] = color[n][m - 1];
}
}
/*
* 重新将第一行的flag[s][0]置为0
*/
for (int s = RussiaGameConstant.LEFT; s <= RussiaGameConstant.RIGHT; s++) {
flag[s][RussiaGameConstant.UP] = 0;
}
}
}
/*
* 将下一个图形作为当前运动的图形,并随机产生下一个图形。
*/
sr1 = sr2;
sr2 = RussiaSquareFactory.generateNextRussiaSquareByRandom();
}
// 计算分数
calculateScore(num);
repaint();
}
}
通过如上的步骤,一个单机版的Swing游戏就实现了,本文描述和实现了三种方块类型的旋转。
大家可以参考上述的变形方法完成其他类型的变化,如L型, Z字形。
游戏效果如下: