俄罗斯方块游戏开发 ——图形化编程java swing 项目

通过java swing技术开发的俄罗斯方块游戏,实现了基础的游戏绘制、移动逻辑、消除逻辑、得分统计、死亡判定、预测方块等功能。

成果展示截图:

        

项目源码及 可执行文件jar 下载 :

https://github.com/echowz/Teris

游戏介绍:

俄罗斯方块的规则:

玩家不断操作下落的不同板块,直达底部,并刷新新的板块。在操作时通过调整位置和旋转方向,使得落下的方块可以填满一整层,每当一层填满,则该层会消失,上方所积累的方块会落下,同时积累分数,当累积到上方空间不足以刷新新的方块,且整个空间无法消除时,则游戏结束

游戏场地10×20的平面网格地图。每个单位为相同宽度的小正方形。

操作图形:一组固定由4个小正方形组成的不同规则的图形,称为Tetromino。共有6种形态,通过字母象形分别记作: I, J, L, S, Z, O, T

I:

 连续的4个方块组成一长条图形,形如字母I。一次最多可以消除4层。共有2种旋转状态。

J:

形如字母J,一次最多可以消除3层,共有4种旋转状态。

L:

形如字母L,J的对称形状,一次最多可以消除3层,共有4种旋转状态

O:

形如字母O,4个小正方形堆叠形成的大正方形,一次最多可以消除2层,仅有一种旋转状态。

S:

形如字母S,一次最多可以消除2层,共有2种旋转状态

 T:

形如字母T,一次最多可以消除2层,有4种旋转状态。

Z:

形如字母Z,一次最多可以消除2层,共有2种旋转状态

分数累计:  一次消除层数的平方*10,1层为10分,2层为40分,3层为90分,4层为160分。

项目结构:

主框体程序结构:

GamePanel 即框体左侧的游戏本体部分,继承自模块Jpanel,负责绘制游戏平面显示。

Menu即框体右侧的菜单信息部分,继承自模块Jpanel负责展示信息。

 

MainFrame 为整个框体,继承自Jframe。

Main 主类中负责创建和编写游戏流程等逻辑。 

 方块样式结构:

抽象类Tetromino描述了一个类型方块的基本特征,坐标,移动方法,旋转方法等。

cell类描述一个单独的小方块,记录坐标。

7个实现类都以 Cell 组成,记录其各个子小方块所处的位置,从而组成了整个方块的形态。

每个Cell方块都拥有其坐标属性

I 方块的组成可以表示为: 以其中一个靠中的方块(x,y)作为整体 I方块 的坐标表示,

 生成 I 方块 对象时,将生成四个Cell对象来组成整体。此时,I 方块 的其中一个旋转状态便表示完毕,但每个类型的方块都可能有多个旋转状态,因此

项目介绍 

框体部分:

 GamePanel

该区域绘制整个版面,用成员变量二维数组graph记录10*20的网格中的信息,当网格的值为0时表示该网格为空,其他正值分别表示不同的颜色,若为-1则该网格为界外网格,也是不显示的网格。由于gamepanel只创建一次,其成员变量采用静态方法,方便全局调用。

 成员变量部分:

    public static int[][] graph;        //地图的二维数组映射
    public static Color[] colors;       //7种方块7种颜色,便于区分和识别。

构造部分:

    public GamePanel()
    {
        init();
        setBackground(Color.CYAN);          //背景色设为淡蓝 方格填充后作为淡蓝色网格线
        setBounds(new Rectangle(0,0,MainFrame.width*2/3,MainFrame.height));//设置panel区域的长宽和位置,为整个框体的左三分之二处
        setVisible(true);
    }
    public void init() //初始化辅助参数,颜色集,地图等。
    {
        colors=new Color[]{ //初始化颜色数组
                Color.white,    //默认静态final变量,返回的是白色的Color对象
                new Color(0,240,240),
                new Color(0,0,240),
                new Color(240,160,0),
                new Color(240,240,0),
                new Color(0,240,0),
                new Color(160,0,240),
                new Color(240,0,0)};
        graph = new int[25][15];    //初始化地图大小

        for(int i=0;i<=22;i++)
        Arrays.fill(graph[i], -1);  //先全部初始化-1,再将合法区域初始化为0

        for(int i=1;i<=20;i++)      //地图的合法区域为x:1-20  y:1-10;
            for(int j=1;j<=10;j++)
                graph[i][j]=0;          //经初始化,-1为出界。
    }

 重写paint方法,整个程序的核心绘制,整个版面的显示:

    @Override
    public void paint(Graphics g)
    {
        super.paint(g);

        for(int i=1;i<=20;i++)
            for(int j=1;j<=10;j++)
            {
                g.setColor(colors[graph[i][j]]);
                int x=MainFrame.width/40+(j-1)*MainFrame.width/16;
                int y=MainFrame.width/20+(i-1)*MainFrame.width/16;
                int width=MainFrame.width/16-2,height=MainFrame.width/16-2;
                g.fillRect(x,y,width,height);
            }
    }

 Menu

menu区域用作信息显示,不作重点讲解,预测下一个方块的功能有一定代码逻辑。与Tetromino的构造结合应用。由于Menu只会创建一个,其成员变量采用静态方式。

成员变量:

    public static int score;    //当前分数
    public static ArrayList<Image> imageList;    //用于显示下一个方块的图片,打印在menu中
    public static int currentPicture;    //当前需要打印的图片为

构造方法中初始化信息,paint中绘制界面。

MainFrame

定义了整个程序需要参考的参数:宽、高

    public static final int height=800;
    public static final int width=height*3/4;

构造函数中设置框体的基本属性即可。

Main

主类中创建的全局变量为:

    public static Tetromino select; //当前操控的方块设置为select
    public static Menu menu;
    public static GamePanel gamePanel;
    public static MainFrame mainFrame;
    public static final Object object = new Object();   //用作synchronized 参数,用于线程阻塞,循环下降操作的等待过程。
    public static int pauseTime=500;        //自动下落的间隔时间,用于控制速度,间隔越短下落速度越快。

 在main方法中创建gamepanel、menu后添加到创建的mainframe中,并添加一个新线程用作不断重绘两个板块:gamepanel和menu中的内容。

public static void main(String[] args) {
        System.out.println("运行开始");
        gamePanel = new GamePanel();
        menu = new Menu();
        mainFrame = new MainFrame();//Mainframe需要在后,因为其中有需要调用到panel的成员的语句

        mainFrame.add(gamePanel);
        mainFrame.add(menu);

        new Thread(()-> {
            while (true) {
                gamePanel.repaint();
                menu.repaint();
            }
        }).start(); //持续刷新画布的线程。
        updateTet();

        Thread downs = new Thread(()->{    //不断循环下落的线程。
            try {
                while (true) {
                    synchronized (object) {
                        down();
                        object.wait(pauseTime/2);       //下落速度
                        object.wait(pauseTime/2);
                    }
                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        });
        downs.start();
...
...

}

在main中添加键盘监听,完善游戏的操作逻辑。

       mainFrame.addKeyListener(new KeyAdapter() {       //监听玩家操作
            @Override
            public void keyPressed(KeyEvent e) {
                switch (e.getKeyChar()) {    //获取键盘活动的char值
                    case 'a':        //左右移动的逻辑,当前选定的Tetromino方块 有抽象方法移动和旋转,移动传递参数01分别为左右移动。
                    case 'A':
                        select.move(0);
                        break;
                    case 'd':
                    case 'D':
                        select.move(1);
                        break;
                    case 's':        //s用作单次点击加速下落
                    case 'S':
                        down();
                        break;
                    case ' ':        //调用当前选定的Tetromino方块的自我旋转方法,实现旋转
                        select.spin();
                        break;
                    case 'w':        //触发/取消 暂停下落
                    case 'W':
                        if (downs.isAlive())
                            synchronized (object) {
                                object.notify();       //下落速度
                            }
                        if (pauseTime == 500) pauseTime = 2000000;
                        else pauseTime = 500;    //通过改变下落间隔实现暂停和继续的功能
                        break;
                }
                }
            }
        );

方块部分:

小方块Cell类

成员变量

即记录当前小方块所处坐标的x和y

    public int x;
    public int y;

成员方法

setColor() 将当前小方块所处的位置的单元格上色。

isBorder()是否处于边界(根据将要移动的方向来判断,即可能是边界,也可能是另一个已填充的方块)

legal()判断这个小方块当前的位置是否是合法的。(在染色前判断,该位置应该是空的才会被染色,因为俄罗斯方块中不存在覆盖颜色的行为,所有只有不出界且为空才合法);

    public void setColor(int color) {       //给当前小方块所在位置上色
        GamePanel.graph[Math.max(0,x)][Math.max(0,y)]=color;
    }
    public boolean isBorder(int dir) {    //参数 0向左移动,1向右移动 2向下 通过该小方块的移动判断整体移动后是否会撞击边界或其他方块 作为碰撞判定的一部分
        if(dir==0) return GamePanel.graph[x][y-1] != 0;
        else if(dir==1) return GamePanel.graph[x][y+1]!=0;
        else if(dir==2) return GamePanel.graph[x+1][y]!=0;
        return true;
    }
    public boolean legal(){     //判断该方块当前位置是否合法。    //合法返回真
        if(x <1 || x > 20 || y <1 || y > 10)return false;
        return GamePanel.graph[x][y]==0;
    }

抽象类Tetromino

拥有的成员变量为:

    public int color;   //颜色
    public int x;       //当前方块的坐标
    public int y;
    public int status;  //当前方块处于的旋转状态为
    public int statusNum=4; //当前方块一定有几个状态
    public static int currentTetromino=new Random().nextInt(7);//当前选定的方块,通过随机数选定。
    public Cell[][] cells;  //当前方块的i个状态,每个状态4个小方块,所记录的小方块信息。
        //旋转时将在第一维,不同的状态之间切换; 移动时将在第二维,对当前状态下的4个方块进行遍历,查看是否可以移动,和修改其位置信息。

Tetromino的核心方法为move方法和spin方法,移动和旋转是俄罗斯方块的核心操作方式,是画面的主要改变方式。

move(int dir)接收参数,通过参数值的不同实现左右下三个方向的移动。遍历所含的4个小方块,假定朝既定方向移动,判断移动后的位置是否合法,如果都合法则实现移动,有不合法的则撤销。

spin()不接收参数,将当前操控的方块进行顺时针90°的旋转。

在实现层面上讲,是将当前选定方块切换至下一个旋转状态,因为每个方块都有成员cell【】【】,也就是每个旋转状态的每个小方块儿子属性,在旋转时遍历所含的小方块判定下个状态是否合法,合法则旋转。

    public void move(int dir) {  //0左 1右 2下
        setColor(0);        //将该方块移除
        for(int j=0;j<4;j++)    
            if(cells[status%statusNum][j].isBorder(dir)) {    //是否为底部,即下面一个单位是否有元素。
                setColor(color);
                return;
            }
        if (dir == 0) y--;
        else if(dir==1) y++;
        else if(dir==2) x++;

        setCells(x,y);
        setColor(color);
    }
    public void spin() {        //旋转
        setColor(0);                    //实现逻辑为先假设旋转,先移除旧的,再切换状态,然后判断旋转后的方块所处位置是否合法(全为0)。如果合法则应用填色,不合法则恢复
        status++;    //下一个状态
        for (int j = 0; j < 4; j++) {   //所有小方块合法才改变。
            if (!cells[status%statusNum][j].legal()) {
                status--;

                setColor(color);
                return;
            }
        }
        setColor(color);
    }

7个实现类

都实现了父类的setcells方法,初始化各个状态的信息。以J方块举例,其完整代码如下:

public class J extends Tetromino{
    public J(int x, int y) {
        super(x,y);
        statusNum=4;
        color=2;
    }

    @Override
    public void setCells(int x, int y) {        //各个形状如下:

        cells[0][0].setLocation(x-1, y-1);                // *
        cells[0][1].setLocation(x, y);                    // * * *
        cells[0][2].setLocation(x, y -1);
        cells[0][3].setLocation(x, y +1);

        cells[1][0].setLocation(x-1, y);               // * *
        cells[1][1].setLocation(x, y);                 // *
        cells[1][2].setLocation(x + 1, y);             // *
        cells[1][3].setLocation(x -1, y+1);

        cells[2][0].setLocation(x+1, y+1);              // * * *
        cells[2][1].setLocation(x, y);                  //     *
        cells[2][2].setLocation(x , y-1);
        cells[2][3].setLocation(x , y+1);

        cells[3][0].setLocation(x-1, y);                //   *
        cells[3][1].setLocation(x, y);                  //   *
        cells[3][2].setLocation(x +1, y);               // * *
        cells[3][3].setLocation(x +1, y-1);
    }
}

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的 Java Swing 实现俄罗斯方块代码示例: ```java import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.Random; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; public class Tetris extends JPanel implements ActionListener, KeyListener { private static final long serialVersionUID = 1L; private final int WIDTH = 10; private final int HEIGHT = 22; private Timer timer; private boolean isFallingFinished = false; private boolean isStarted = false; private boolean isPaused = false; private int numLinesRemoved = 0; private int curX = 0; private int curY = 0; private JLabel statusbar; private Shape curPiece; private Shape.Tetrominoes[] board; public Tetris(JFrame parent) { initTetris(parent); } private void initTetris(JFrame parent) { setFocusable(true); curPiece = new Shape(); timer = new Timer(400, this); timer.start(); statusbar = new JLabel(" 0"); parent.add(statusbar, BorderLayout.SOUTH); board = new Shape.Tetrominoes[WIDTH * HEIGHT]; addKeyListener(this); clearBoard(); } public void actionPerformed(ActionEvent e) { if (isFallingFinished) { isFallingFinished = false; newPiece(); } else { oneLineDown(); } } private int squareWidth() { return (int) getSize().getWidth() / WIDTH; } private int squareHeight() { return (int) getSize().getHeight() / HEIGHT; } private Shape.Tetrominoes shapeAt(int x, int y) { return board[(y * WIDTH) + x]; } public void start() { if (isPaused) return; isStarted = true; isFallingFinished = false; numLinesRemoved = 0; clearBoard(); newPiece(); timer.start(); } private void pause() { if (!isStarted) return; isPaused = !isPaused; if (isPaused) { timer.stop(); statusbar.setText("paused"); } else { timer.start(); statusbar.setText(String.valueOf(numLinesRemoved)); } repaint(); } public void paint(Graphics g) { super.paint(g); Dimension size = getSize(); int boardTop = (int) size.getHeight() - HEIGHT * squareHeight(); for (int i = 0; i < HEIGHT; ++i) { for (int j = 0; j < WIDTH; ++j) { Shape.Tetrominoes shape = shapeAt(j, HEIGHT - i - 1); if (shape != Shape.Tetrominoes.NoShape) drawSquare(g, j * squareWidth(), boardTop + i * squareHeight(), shape); } } if (curPiece.getShape() != Shape.Tetrominoes.NoShape) { for (int i = 0; i < 4; ++i) { int x = curX + curPiece.getX(i); int y = curY - curPiece.getY(i); drawSquare(g, x * squareWidth(), boardTop + (HEIGHT - y - 1) * squareHeight(), curPiece.getShape()); } } } private void dropDown() { int newY = curY; while (newY > 0) { if (!tryMove(curPiece, curX, newY - 1)) break; --newY; } pieceDropped(); } private void oneLineDown() { if (!tryMove(curPiece, curX, curY - 1)) pieceDropped(); } private void clearBoard() { for (int i = 0; i < HEIGHT * WIDTH; ++i) board[i] = Shape.Tetrominoes.NoShape; } private void pieceDropped() { for (int i = 0; i < 4; ++i) { int x = curX + curPiece.getX(i); int y = curY - curPiece.getY(i); board[(y * WIDTH) + x] = curPiece.getShape(); } removeFullLines(); if (!isFallingFinished) newPiece(); } private void newPiece() { curPiece.setRandomShape(); curX = WIDTH / 2 + 1; curY = HEIGHT - 1 + curPiece.minY(); if (!tryMove(curPiece, curX, curY)) { curPiece.setShape(Shape.Tetrominoes.NoShape); timer.stop(); isStarted = false; statusbar.setText("game over"); } } private boolean tryMove(Shape newPiece, int newX, int newY) { for (int i = 0; i < 4; ++i) { int x = newX + newPiece.getX(i); int y = newY - newPiece.getY(i); if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return false; if (shapeAt(x, y) != Shape.Tetrominoes.NoShape) return false; } curPiece = newPiece; curX = newX; curY = newY; repaint(); return true; } private void removeFullLines() { int numFullLines = 0; for (int i = HEIGHT - 1; i >= 0; --i) { boolean lineIsFull = true; for (int j = 0; j < WIDTH; ++j) { if (shapeAt(j, i) == Shape.Tetrominoes.NoShape) { lineIsFull = false; break; } } if (lineIsFull) { ++numFullLines; for (int k = i; k < HEIGHT - 1; ++k) { for (int j = 0; j < WIDTH; ++j) board[(k * WIDTH) + j] = shapeAt(j, k + 1); } } } if (numFullLines > 0) { numLinesRemoved += numFullLines; statusbar.setText(String.valueOf(numLinesRemoved)); isFallingFinished = true; curPiece.setShape(Shape.Tetrominoes.NoShape); repaint(); } } private void drawSquare(Graphics g, int x, int y, Shape.Tetrominoes shape) { Color colors[] = { new Color(0, 0, 0), new Color(204, 102, 102), new Color(102, 204, 102), new Color(102, 102, 204), new Color(204, 204, 102), new Color(204, 102, 204), new Color(102, 204, 204), new Color(218, 170, 0) }; Color color = colors[shape.ordinal()]; g.setColor(color); g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2); g.setColor(color.brighter()); g.drawLine(x, y + squareHeight() - 1, x, y); g.drawLine(x, y, x + squareWidth() - 1, y); g.setColor(color.darker()); g.drawLine(x + 1, y + squareHeight() - 1, x + squareWidth() - 1, y + squareHeight() - 1); g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1, x + squareWidth() - 1, y + 1); } public void keyPressed(KeyEvent e) { if (!isStarted || curPiece.getShape() == Shape.Tetrominoes.NoShape) { return; } int keycode = e.getKeyCode(); if (keycode == 'p' || keycode == 'P') { pause(); return; } if (isPaused) return; switch (keycode) { case KeyEvent.VK_LEFT: tryMove(curPiece, curX - 1, curY); break; case KeyEvent.VK_RIGHT: tryMove(curPiece, curX + 1, curY); break; case KeyEvent.VK_DOWN: tryMove(curPiece.rotateRight(), curX, curY); break; case KeyEvent.VK_UP: tryMove(curPiece.rotateLeft(), curX, curY); break; case KeyEvent.VK_SPACE: dropDown(); break; case 'd': case 'D': oneLineDown(); break; } } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { } } class Shape { enum Tetrominoes { NoShape, ZShape, SShape, LineShape, TShape, SquareShape, LShape, MirroredLShape }; private Tetrominoes pieceShape; private int coords[][]; private int[][][] coordsTable; public Shape() { coords = new int[4][2]; setShape(Tetrominoes.NoShape); } public void setShape(Tetrominoes shape) { coordsTable = new int[][][] { { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }, { { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, 1 } }, { { 0, -1 }, { 0, 0 }, { 1, 0 }, { 1, 1 } }, { { 0, -1 }, { 0, 0 }, { 0, 1 }, { 0, 2 } }, { { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 } }, { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }, { { -1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } }, { { 1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } } }; for (int i = 0; i < 4; i++) { for (int j = 0; j < 2; ++j) { coords[i][j] = coordsTable[shape.ordinal()][i][j]; } } pieceShape = shape; } private void setX(int index, int x) { coords[index][0] = x; } private void setY(int index, int y) { coords[index][1] = y; } public int x(int index) { return coords[index][0]; } public int y(int index) { return coords[index][1]; } public Tetrominoes getShape() { return pieceShape; } public void setRandomShape() { Random r = new Random(); int x = Math.abs(r.nextInt()) % 7 + 1; Tetrominoes[] values = Tetrominoes.values(); setShape(values[x]); } public int minX() { int m = coords[0][0]; for (int i = 0; i < 4; i++) { m = Math.min(m, coords[i][0]); } return m; } public int minY() { int m = coords[0][1]; for (int i = 0; i < 4; i++) { m = Math.min(m, coords[i][1]); } return m; } public Shape rotateLeft() { if (pieceShape == Tetrominoes.SquareShape) return this; Shape result = new Shape(); result.pieceShape = pieceShape; for (int i = 0; i < 4; ++i) { result.setX(i, y(i)); result.setY(i, -x(i)); } return result; } public Shape rotateRight() { if (pieceShape == Tetrominoes.SquareShape) return this; Shape result = new Shape(); result.pieceShape = pieceShape; for (int i = 0; i < 4; ++i) { result.setX(i, -y(i)); result.setY(i, x(i)); } return result; } public int getX(int index) { return coords[index][0]; } public int getY(int index) { return coords[index][1]; } } public class TetrisGame extends JFrame { private static final long serialVersionUID = 1L; public TetrisGame() { initUI(); } private void initUI() { Tetris board = new Tetris(this); add(board); setTitle("Tetris"); setSize(400, 700); setDefaultCloseOperation(EXIT_ON_CLOSE); setLocationRelativeTo(null); } public static void main(String[] args) { TetrisGame game = new TetrisGame(); game.setVisible(true); } } ``` 这个代码示例可以直接运行,实现了一个简单的俄罗斯方块游戏

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值