使用Java语言编写的经典游戏俄罗斯方块,能够调整游戏难度、加快下落速度、左右旋转方块等全部功能。(代码已集成在一个class内,如下所示,点击即可运行。内容水平有限,欢迎指正批评。)
演示视频 https://www.bilibili.com/video/BV1Yi4y1R7Xn/
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
public class Tetris extends JFrame{
/**
* D、F变形,方向键移动,空格暂停,Esc退出游戏。
*/
private static final long serialVersionUID = 1L;
private JButton[][] grid = new JButton[20][10];//背景
private int[][][][] relativePosition={{{{0,0},{0,1},{0,2},{0,3}},{{0,0},{1,0},{2,0},{3,0}}},
{{{0,0},{1,0},{1,1},{1,2}},{{0,1},{1,1},{2,0},{2,1}},{{0,0},{0,1},{0,2},{1,2}},{{0,0},{0,1},{1,0},{2,0}}},
{{{0,1},{1,0},{1,1},{1,2}},{{0,1},{1,0},{1,1},{2,1}},{{0,0},{0,1},{0,2},{1,1}},{{0,0},{1,0},{1,1},{2,0}}},
{{{0,2},{1,0},{1,1},{1,2}},{{0,0},{0,1},{1,1},{2,1}},{{0,0},{0,1},{0,2},{1,0}},{{0,0},{1,0},{2,0},{2,1}}},
{{{0,0},{0,1},{1,1},{1,2}},{{0,1},{1,0},{1,1},{2,0}}},
{{{0,0},{0,1},{1,0},{1,1}}},
{{{0,1},{0,2},{1,0},{1,1}},{{0,0},{1,0},{1,1},{2,1}}}};//七种不同方块组合,每种方块组合的旋转角度,每种旋转角度的四个不同方块,每个方块的行列坐标
private boolean[][] gridState = new boolean[20][10];//网格是否被填充,true表示被填充,false表示没有被填充
private boolean falling = false;//当前是否有方块下降
private boolean pause = false;//当前游戏是否暂停
private boolean token = false;//当前令牌是否被占用
private boolean fallToTheGround;//当前是否落到地上或者其他方格上
private boolean transformative;//当前能否变形
private boolean removable;//当前能否左右移动
private boolean gameOver = false;//当前游戏是否结束(结束条件两个,一个是达到了10000分,一个是触到了天花板)
private int[][] location = new int[4][2];//下降的四个方块各自坐标
private int time = 1000;//方块停留时间,与游戏难度相关
private int score = 0;//游戏得分
private int realTimePlaceL, realTimePlaceR;//方块整体的基准点
private int shapeKind, angle, angle_ = -1, angleNum;//形状的种类,旋转角度,将要旋转到的角度,此种类能够旋转的角度数
private Color lightColor = new Color(210,210,210);//浅色方块
private Color darkColor = new Color(20,20,20);//深色背景
private String difficulty;//游戏难度
public Tetris() {
chooseDifficulty();//选择难度
interfaceMethod();//渲染UI
systemTimer();//系统控制方块定时下降
play();//玩家用键盘操作方块
}
void chooseDifficulty() {//选择难度
Object[] possibleValues = {"青铜", "白银", "黄金", "白金", "钻石"};
Object selectedValue = JOptionPane.showInputDialog(null,
"按键说明:\n\n D、F变形,方向键移动,\n 空格暂停,Esc退出游戏。\n\n请选择难度等级:",
"开始",JOptionPane.PLAIN_MESSAGE, null,possibleValues, possibleValues[1]);
if(selectedValue == null) {
System.exit(0);
}else {
switch(difficulty = selectedValue.toString()) {
case "青铜":
time = 1500;//每个位置方块组合停留1500ms
break;
case "白银":
time = 1000;//每个位置方块组合停留1000ms
break;
case "黄金":
time = 500;//每个位置方块组合停留500ms
break;
case "白金":
time = 300;//每个位置方块组合停留300ms
break;
case "钻石":
time = 150;//每个位置方块组合停留150ms
break;
}
}
}
void interfaceMethod() {//渲染UI
setTitle("Tetris 难度: " + difficulty + " 得分: " + String.valueOf(score));//设置窗口标题
setResizable(false);//首先隐藏窗口,等全部组件渲染完一起显示
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);//设置窗口关闭按钮不起作用,便于下面重写关闭功能
int width = Toolkit.getDefaultToolkit().getScreenSize().width;//获取屏幕宽度
int height = Toolkit.getDefaultToolkit().getScreenSize().height;//获取屏幕高度
int windowsWedth =360, windowsHeight = 700;//窗口大小
setBounds((width - windowsWedth) / 2,(height - windowsHeight) / 2,windowsWedth,windowsHeight);//设置屏幕居中
setLayout(new GridLayout(0,10));//网格布局,10列
addWindowListener(new WindowAdapter() {//重写窗口关闭
public void windowClosing(WindowEvent e) {
pause = true;//暂停游戏
Object[] options = {"继续游戏", "重新开始", "退出游戏"};
Object selectedValue = JOptionPane.showOptionDialog(null, "是否退出游戏?", "游戏已暂停",
JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
switch(selectedValue.toString()) {
case "-1"://关闭弹窗
case "0"://继续游戏
pause = false;
break;
case "1"://重新开始
dispose();//释放当前窗口
new Tetris();//重新加载
break;
default:
System.exit(0);//退出程序
break;
}
}
});
for(int i=0; i<20; i++) {
for(int j=0; j<10; j++) {
grid[i][j] = new JButton("");//创建按钮
getContentPane().add(grid[i][j]);//添加进网格布局
grid[i][j].setBackground(darkColor);//设置为深色背景
grid[i][j].setEnabled(false);//去除按钮可操作性,单纯作为背景(用按钮比图片或者面板更方便一些,时间差别不大)
}
}
setVisible(true);//显示窗口
}
void systemTimer() {//系统控制方块定时下降
Timer timer = new Timer(); //创建一个定时器
timer.schedule(new TimerTask() {//为该定时器添加任务
public void run() {
if(!pause) {//游戏未暂停
while(true) {
if(!token) {//如果令牌忙碌,则等待令牌;如果令牌空闲,占据令牌。
token = true;
if(falling) {
fall();//方块处于下落状态则调用下落方法,不处于则调用生成新方块方法
}else {
generate();
}
token = false;//释放令牌
break;
}
}
}
if(gameOver) {//游戏结束
timer.cancel();
Object[] options = {"重新开始", "退出游戏"};
int re = JOptionPane.showOptionDialog(null, "是否重新开始", "游戏结束",
JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
if(re == JOptionPane.YES_OPTION){
dispose();
new Tetris();
}else{
System.exit(0);
}
}
}
}, 0, time);
}
void play() {//玩家用键盘操作方块
addKeyListener(new KeyAdapter(){//添加键盘事件
public void keyPressed(KeyEvent e){
if(e.getKeyCode() == KeyEvent.VK_ESCAPE) {//直接退出游戏
System.exit(0);
}else {
if(!gameOver) {//首先判断游戏是否结束
if(e.getKeyCode() == KeyEvent.VK_SPACE){//空格将游戏设置为暂停
if(pause) {
pause = false;
}else {
pause = true;
}
}else {
if(!pause) {//暂停时不能使用方向键,只能使用Esc键和空格键
while(falling && true) {//只有在方块下落过程中才可以申请令牌,响应键盘操作(方块落地一段时间还属于下落状态还能移动,
if(!token) { //但是如果方块组合的一部分和下面的一行消除掉,则不属于下落状态不能移动了)
token = true;//占据令牌
switch(e.getKeyCode()) {
case KeyEvent.VK_LEFT:
if(location[0][1]-1>-1 && location[1][1]-1>-1 && location[2][1]-1>-1 && location[3][1]-1>-1) {//判断左移后是否超过地图
removable = true;//判断左移后是否和其他方块组合重叠
for(int i=0; i<4; i++) {//考察方块组合的每个方块
if(gridState[location[i][0]][location[i][1]-1]) {
removable = false;
break;
}
}
if(removable) {//未重叠则允许左移
realTimePlaceR--;//方块整体的基准点左移
for(int i=0; i<4; i++) {
grid[location[i][0]][location[i][1]].setBackground(darkColor);//原来方块位置恢复成背景
location[i][1]--;//方块整体左移
}
for(int i=0; i<4; i++) {
grid[location[i][0]][location[i][1]].setBackground(lightColor);//渲染方块
}
}
}
break;
case KeyEvent.VK_RIGHT:
if(location[0][1]+1<10 && location[1][1]+1<10 && location[2][1]+1<10 && location[3][1]+1<10) {//判断右移后是否超过地图
removable = true;//判断右移后是否和其他方块组合重叠
for(int i=0; i<4; i++) {//考察方块组合的每个方块
if(gridState[location[i][0]][location[i][1]+1]) {
removable = false;
break;
}
}
if(removable) {//未重叠则允许右移
realTimePlaceR++;//方块整体的基准点右移
for(int i=0; i<4; i++) {
grid[location[i][0]][location[i][1]].setBackground(darkColor);//原来方块位置恢复成背景
location[i][1]++;//方块整体右移
}
for(int i=0; i<4; i++) {
grid[location[i][0]][location[i][1]].setBackground(lightColor);//渲染方块
}
}
}
break;
case KeyEvent.VK_DOWN:
fall();//下落方法
break;
case KeyEvent.VK_D:
angle_ = (angle + 1) % angleNum;//将要逆时针旋转到的位置
case KeyEvent.VK_F:
if(angle_ == -1) {//如果不为-1说明点击的是D,为-1说明点击的是F(顺时针)
angle_ = (angle + angleNum - 1) % angleNum;
}
if(angleNum != 1) {//旋转角度只有一个的话就不转了(正方形方块组合)
transformative = true;//预先假设能变形
for(int i=0; i<4; i++) {//考察方块组合的每个方块
int L = relativePosition[shapeKind][angle_][i][0] + realTimePlaceL;//变形后的行坐标
int R = relativePosition[shapeKind][angle_][i][1] + realTimePlaceR;//变形后的列坐标
if(L>19 || R>9 || gridState[L][R]) {//超过地图或者该位置有其他方块则不能变形
transformative = false;
break;
}
}
if(transformative) {//符合变形要求
angle = angle_;//更新角度
for(int i=0; i<4; i++) {
grid[location[i][0]][location[i][1]].setBackground(darkColor);//原来方块位置恢复成背景
location[i][0]++;
}
for(int i=0; i<4; i++) {
location[i][0] = relativePosition[shapeKind][angle][i][0] + realTimePlaceL;//更新行位置
location[i][1] = relativePosition[shapeKind][angle][i][1] + realTimePlaceR;//更新列位置
grid[location[i][0]][location[i][1]].setBackground(lightColor);//渲染方块
}
}
}
angle_ = -1;//重新设置为-1
break;
}
token = false;//释放令牌
break;
}
}
}
}
}
}
}
});
}
void fall() {
fallToTheGround = false;//是否落到地上或者其他方格上
for(int i=0; i<4; i++) {
if(location[i][0] == 19 || gridState[location[i][0]+1][location[i][1]]) {
fallToTheGround = true;
break;
}
}
if(fallToTheGround) {
falling = false;
for(int i=0; i<4; i++) {
gridState[location[i][0]][location[i][1]] = true;
}
removeLine(19);
for(int j=0; j<9; j++) {
if(gridState[0][j]) {
gameOver = true;
break;
}
}
}else {
realTimePlaceL++;
for(int i=0; i<4; i++) {
grid[location[i][0]][location[i][1]].setBackground(darkColor);
location[i][0]++;
}
for(int i=0; i<4; i++) {
grid[location[i][0]][location[i][1]].setBackground(lightColor);
}
}
}
void generate() {
realTimePlaceL = 0;
realTimePlaceR = 4;
falling = true;
shapeKind = (int) (Math.random()*7);
switch(shapeKind) {
case 5:
angleNum = 1;
angle = 0;
break;
case 0:
case 4:
case 6:
angleNum = 2;
angle = (int) (Math.random()*2);
break;
default:
angleNum = 4;
angle = (int) (Math.random()*4);
break;
}
for(int i=0; i<4; i++) {
location[i][0] = relativePosition[shapeKind][angle][i][0] + realTimePlaceL;
location[i][1] = relativePosition[shapeKind][angle][i][1] + realTimePlaceR;
grid[location[i][0]][location[i][1]].setBackground(lightColor);
}
}
void removeLine(int line) {
boolean removed = true, allBlank;
int line_=line;
for(int i=line; i>=0; i--) {
removed = true;
allBlank = true;
for(int j=0; j<10; j++) {
if(gridState[i][j]) {
allBlank = false;
}else {
removed = false;
}
if(!removed && !allBlank) {
break;
}
}
if(removed || allBlank) {
line_ = i;
break;
}
}
if(removed) {
for(int i=line_; i>0; i--) {
for(int j=0; j<10; j++) {
if(gridState[i][j] ^ gridState[i-1][j]) {
gridState[i][j] = gridState[i-1][j];
if(gridState[i][j]) {
grid[i][j].setBackground(lightColor);
}else {
grid[i][j].setBackground(darkColor);
}
}
}
}
score += 10;
if(score == 10000) {
gameOver = true;
}else {
setTitle("Tetris 难度: " + difficulty + " 得分: " + String.valueOf(score));//设置窗口标题
removeLine(line_);
}
}
}
public static void main(String[] args) {
new Tetris();
}
}