Java | Tetris

Posted 自然生长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java | Tetris相关的知识,希望对你有一定的参考价值。

基于 Java-Swing 实现俄罗斯方块

Preview

普通模式

技术分享图片

加速模式

技术分享图片

设计思路

方块的属性

  • 方块表示:
    • 由四维数组 SHAPE[][][][] 表示7种方块及每种方块的4种翻转状态。
    • SHAPE[type][state]可以唯一标识一个方块。
  • 方块属性:
    • type:方块类型
    • state:方块翻转状态
    • (x, y):方块坐标
    • nextType:下一方块类型
    • nextState:下一方块翻转状态
  • 背景块:由map[ROW][COl]表示已固定的背景块。

方块的行为

方块翻转:通过改变方块的state属性实现翻转

public void turn() {
    int temp = state;
    state = (state + 1) % 4;
    // 如果旋转后不合法,还原上一状态
    if (!check(type, state, x, y)) {
        state = temp;
    }
}

方块下落:通过改变方块纵坐标实现下落

public void down() {
    // 如果下一个下落状态合法,则下落;不合法,则固定,清行,新建块。
    if (check(type, state, x, y + 1)) {
        y++;
    } else {
        fix(type, state, x, y);
        clearLines();
        createShape();
    }
    this.repaint();
}

方块左移:通过改变方块横坐标实现左移

public void left() {
    if (check(type, state, x - 1, y)) {
        x--;
    }
}

方块右移:通过改变方块横坐标实现右移

public void right() {
    if (check(type, state, x + 1, y)) {
        x++;
    }
}

核心方法实现

方块碰撞检测

private boolean check(int type, int state, int x, int y) {
    for (int i = 0; i < SHAPE[type][state].length; i++) {
        for (int j = 0; j < SHAPE[type][state][0].length; j++) {
            if (SHAPE[type][state][i][j] == 1) {
                // 在坐标系中小方块坐标(x+j,y+i);在背景矩阵中小方块位置map[y+i][x+j];
                if ((x + j >= COL) || (x + j < 0) || (y + i >= ROW) || (map[y + i][x + j] == 1)) {
                    return false;
                }
            }
        }
    }
    return true;
}

固定当前方块到背景中

private void fix(int type, int state, int x, int y) {
    for (int i = 0; i < SHAPE[type][state].length; i++) {
        for (int j = 0; j < SHAPE[type][state][0].length; j++) {
            // 在坐标系中小方块坐标(x+j,y+i);在背景矩阵中小方块位置map[y+i][x+j];
            if ((y + i < ROW) && (x + j >= 0) && (x + j < COL) && (map[y + i][x + j] == 0)) {
                map[y + i][x + j] = SHAPE[type][state][i][j];
                mapColor[y + i][x + j] = color[type];
            }
        }
    }
}

清行加分

private void clearLines() {
    int lines = 0;
    boolean isFull = true;
    for (int i = 0; i < map.length; i++) {
        isFull = true;
        for (int j = 0; j < map[0].length; j++) {
            if (map[i][j] == 0) {
                isFull = false;
                break;
            }
        }
        if (isFull) {
            lines++;
            for (int m = i; m > 0; m--) {
                for (int n = 0; n < map[0].length; n++) {
                    map[m][n] = map[m - 1][n];
                }
            }
        }
    }
    score += lines * lines * 10;
    if (isAccelMode) {
        up();
    }
}

GUI

绘制图形:重写paint()方法,使用Graphics类随意绘制。

Graphics类绘制方法如下:

setColor(Color color);  // 设置画笔颜色
drawRect(int x,int y,int width,int height);  // 绘制矩形边框
fillRect(int x,int y,int width,int height);  // 填充矩形
fill3DRect(int x,int y,int width,int height, boolean raised); // 填充3D矩形。
drawLine(x1,y1,x2,y2);  // 绘制线条
setFont(Font font); // 设置字体
drawString(String text, int x, int y);  // 绘制字符串

文件目录

com.lizich.tetris下:

.
|——TetrisMain  # 游戏窗口,由此运行
|——TetrisCtrl  # 绘制游戏面板&游戏逻辑

com.lizi.tetris.singleclass下:

.
|——Tetris # 合并了TetrisMain与TetrisCtrl

源码

TetrisMain

package com.liziczh.tetris;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;

public class TetrisMain extends JFrame {

    /**
     * 版本号
     */
    private static final long serialVersionUID = 1L;
    // 游戏面板
    private TetrisCtrl tCtrl = new TetrisCtrl();

    /**
     * 游戏窗口初始化
     */
    public TetrisMain() {
        // 设置标题
        this.setTitle("Lizi Tetris");
        // 设置大小
        this.setSize(tCtrl.getSize());
        // 调用方法居中
        this.setLocationRelativeTo(null);
        // 设置关闭操作:关闭窗口,程序结束运行;
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 设置窗体大小不改变
        this.setResizable(false);
        // 添加键盘监听事件
        this.addKeyListener(keyListener);

        // 菜单栏
        JMenuBar menu = new JMenuBar();
        this.setJMenuBar(menu);
        JMenu gameMenu = new JMenu("游戏");
        JMenuItem newGameItem = gameMenu.add("新游戏");
        newGameItem.addActionListener(newGameAction);
        JMenuItem pauseItem = gameMenu.add("暂停");
        pauseItem.addActionListener(pauseAction);
        JMenuItem continueItem = gameMenu.add("继续");
        continueItem.addActionListener(continueAction);
        JMenuItem exitItem = gameMenu.add("退出");
        exitItem.addActionListener(exitAction);
        JMenu modeMenu = new JMenu("模式");
        JMenuItem normalModeItem = modeMenu.add("普通模式");
        normalModeItem.addActionListener(normalModeAction);
        JMenuItem accelModeItem = modeMenu.add("加速模式");
        accelModeItem.addActionListener(accelModeAction);
        JMenu helpMenu = new JMenu("帮助");
        JMenuItem aboutItem = helpMenu.add("关于");
        aboutItem.addActionListener(aboutAction);
        menu.add(gameMenu);
        menu.add(modeMenu);
        menu.add(helpMenu);
        // 设置窗口可见
        this.setVisible(true);
        // 添加TetrisPanel
        this.add(tCtrl);

    }

    // 键盘事件监听
    KeyListener keyListener = new KeyAdapter() {

        @Override
        public void keyPressed(KeyEvent e) {
            switch (e.getKeyCode()) {
            case KeyEvent.VK_UP:
                // ↑:旋转
                tCtrl.turn();
                tCtrl.repaint();
                break;
            case KeyEvent.VK_LEFT:
                // ←:左移
                tCtrl.left();
                tCtrl.repaint();
                break;
            case KeyEvent.VK_RIGHT:
                // →:右移
                tCtrl.right();
                tCtrl.repaint();
                break;
            case KeyEvent.VK_DOWN:
                // ↓:下移
                tCtrl.down();
                tCtrl.repaint();
                break;
            }
        }
    };

    // 新游戏
    ActionListener newGameAction = new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent arg0) {
            tCtrl.init();
        }
    };
    // 暂停
    ActionListener pauseAction = new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent arg0) {
            tCtrl.setPause();
        }
    };
    // 继续
    ActionListener continueAction = new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            tCtrl.setContinue();
        }
    };

    // 退出
    ActionListener exitAction = new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            System.exit(0);
        }
    };

    // 普通模式
    ActionListener normalModeAction = new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            tCtrl.setNormal();
        }
    };

    // 加速模式
    ActionListener accelModeAction = new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            tCtrl.setAccel();
        }
    };

    // 关于
    ActionListener aboutAction = new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            JOptionPane.showMessageDialog(tCtrl, "Tetris v1.0 from liziczh", "关于", getDefaultCloseOperation());
        }
    };

    public static void main(String[] args) {
        new TetrisMain();
    }

}

TetrisCtrl

package com.liziczh.tetris;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.Timer;

public class TetrisCtrl extends JPanel {

    /**
     * 版本号
     */
    private static final long serialVersionUID = 1L;

    // 方块边长,单位像素(px)
    public final int LEN = 24;

    // Panel区域:20行10列
    public final int ROW = 20;
    public final int COL = 10;

    /**
     * SHAPE[type][state]:方块形状; type方块类型, state方块旋转状态;
     */
    private final int[][][][] SHAPE = new int[][][][] {
            // S:
            { { { 0, 1, 1, 0 }, { 1, 1, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
                    { { 1, 0, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } },
                    { { 0, 1, 1, 0 }, { 1, 1, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
                    { { 1, 0, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } } },
            // Z:
            { { { 1, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
                    { { 0, 1, 0, 0 }, { 1, 1, 0, 0 }, { 1, 0, 0, 0 }, { 0, 0, 0, 0 } },
                    { { 1, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
                    { { 0, 1, 0, 0 }, { 1, 1, 0, 0 }, { 1, 0, 0, 0 }, { 0, 0, 0, 0 } } },
            // L:
            { { { 1, 0, 0, 0 }, { 1, 0, 0, 0 }, { 1, 1, 0, 0 }, { 0, 0, 0, 0 } },
                    { { 1, 1, 1, 0 }, { 1, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
                    { { 1, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } },
                    { { 0, 0, 1, 0 }, { 1, 1, 1, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } } },
            // J:
            { { { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 1, 1, 0, 0 }, { 0, 0, 0, 0 } },
                    { { 1, 0, 0, 0 }, { 1, 1, 1, 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 } },
                    { { 1, 1, 1, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } } },
            // I:
            { { { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 } },
                    { { 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 } },
                    { { 0, 0, 0, 0 }, { 1, 1, 1, 1 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } } },
            // O:
            { { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } },
                    { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } },
                    { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } },
                    { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } } },
            // T:
            { { { 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 } },
                    { { 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 } } },

    };

    // 当前方块的四个参数:type方块类型,state方块旋转状态,坐标(x,y)。
    private int type, state, x, y;
    // 下一个块的参数:nextType方块类型,nextState方块旋转状态
    private int nextType, nextState;

    // 背景:已固定块
    private int[][] map = new int[ROW][COL];
    // 已固定块的颜色
    private Color[][] mapColor = new Color[ROW][COL];

    // 得分
    private int score = 0;
    // 等级
    private int level = 0;
    // 下落延时
    private int delay = 1000;

    // 是否在暂停状态
    private boolean isPause = false;

    // 是否为加速模式
    private boolean isAccelMode = false;

    // 方块颜色:color[type]
    private Color[] color = new Color[] { Color.green, Color.red, Color.orange, Color.blue, Color.cyan, Color.yellow,
            Color.magenta, Color.gray };

    public TetrisCtrl() {
        // 初始化Panel大小
        this.setSize(LEN * 20, LEN * 25);
        this.init();
    }

    /**
     * 游戏初始化
     */
    public void init() {
        // 初始化背景map
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[0].length; j++) {
                map[i][j] = 0;
            }
        }
        // 初始化分数
        score = 0;
        isPause = false;
        // 随机生成下一方块
        nextType = (int) (Math.random() * 7);
        nextState = (int) (Math.random() * 4);
        // 生成当前方块
        createShape();

        // 启动timer
        timer.start();
        // 绘图
        this.repaint();
    }

    /**
     * 创建一个新方块
     */
    public void createShape() {
        // 当前块
        type = nextType;
        state = nextState;
        x = 3;
        y = 0;
        // 下一块
        nextType = (int) (Math.random() * 7);
        nextState = (int) (Math.random() * 4);
        // 如果新块不合法,则表示游戏已结束,则重新开始
        if (!check(type, state, x, y)) {
            JOptionPane.showMessageDialog(this, "GAME OVER!");
            init();
        }
    }

    /**
     * 判断方块是否合法:true合法,false不合法
     */
    private boolean check(int type, int state, int x, int y) {
        for (int i = 0; i < SHAPE[type][state].length; i++) {
            for (int j = 0; j < SHAPE[type][state][0].length; j++) {
                if (SHAPE[type][state][i][j] == 1) {
                    // 在坐标系中小方块坐标(x+j,y+i);在背景矩阵中小方块位置map[y+i][x+j];
                    if ((x + j >= COL) || (x + j < 0) || (y + i >= ROW) || (map[y + i][x + j] == 1)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * 固定shape到map
     */
    private void fix(int type, int state, int x, int y) {
        for (int i = 0; i < SHAPE[type][state].length; i++) {
            for (int j = 0; j < SHAPE[type][state][0].length; j++) {
                // 在坐标系中小方块坐标(x+j,y+i);在背景矩阵中小方块位置map[y+i][x+j];
                if ((y + i < ROW) && (x + j >= 0) && (x + j < COL) && (map[y + i][x + j] == 0)) {
                    map[y + i][x + j] = SHAPE[type][state][i][j];
                    mapColor[y + i][x + j] = color[type];
                }
            }
        }
    }

    /**
     * 消行加分
     */
    private void clearLines() {
        int lines = 0;
        boolean isFull = true;
        for (int i = 0; i < map.length; i++) {
            isFull = true;
            for (int j = 0; j < map[0].length; j++) {
                if (map[i][j] == 0) {
                    isFull = false;
                    break;
                }
            }
            if (isFull) {
                lines++;
                for (int m = i; m > 0; m--) {
                    for (int n = 0; n < map[0].length; n++) {
                        map[m][n] = map[m - 1][n];
                    }
                }
            }
        }
        score += lines * lines * 10;
        if (isAccelMode) {
            up();
        }

    }

    /**
     * 升级加速:UP
     */
    public void up() {
        int limit = 50;
        if (score > limit * level) {
            level++;
            delay /= 1.5;
            timer.setDelay(delay);
            limit = limit * level;
        }
    }

    public void turn() {
        int temp = state;
        state = (state + 1) % 4;
        // 如果旋转后不合法,还原上一状态
        if (!check(type, state, x, y)) {
            state = temp;
        }
    }

    public void down() {
        // 如果下一个下落状态合法,则下落;不合法,则固定。
        if (check(type, state, x, y + 1)) {
            y++;
        } else {
            fix(type, state, x, y);
            clearLines();
            createShape();
        }
        this.repaint();
    }

    public void right() {
        if (check(type, state, x + 1, y)) {
            x++;
        }
    }

    public void left() {
        if (check(type, state, x - 1, y)) {
            x--;
        }
    }

    /**
     * 绘图:重写paint()方法
     * 
     * @see javax.swing.JComponent#paint(java.awt.Graphics)
     */
    @Override
    public void paint(Graphics g) {
        // 游戏区域的左、上边距
        int MARGIN_LEFT = LEN;
        int MARGIN_TOP = LEN + 10;

        // 边栏的起始坐标
        int SIDEBAR_X = LEN * 13;// 文本的横坐标
        int SIDEBAR_Y = LEN * 9; // 文本的纵坐标
        /**
         * 填充背景色
         */
        g.setColor(Color.white);
        g.fillRect(0, 0, (int) (this.getSize().getWidth()), (int) (this.getSize().getHeight()));

        /**
         * 画边框
         */
        g.setColor(Color.gray);
        for (int offset = 1; offset < 2; offset++) {
            // 绘制矩形边框:drawRect(int x,int y,int width,int height);
            g.drawRect(MARGIN_LEFT - offset, MARGIN_TOP - offset, COL * LEN + offset * 2, ROW * LEN + offset * 2);
        }
        /**
         * 画网状线
         */
        g.setColor(Color.gray);
        // 11条竖线
        for (int i = 0; i < 11; i++) {
            // 绘制线条:drawLine(x1,y1,x2,y2);
            g.drawLine(MARGIN_LEFT + LEN * i, MARGIN_TOP, MARGIN_LEFT + LEN * i, MARGIN_TOP + ROW * LEN);
        }
        // 21条横线
        for (int i = 0; i < 21; i++) {
            g.drawLine(MARGIN_LEFT, MARGIN_TOP + LEN * i, MARGIN_LEFT + COL * LEN, MARGIN_TOP + LEN * i);
        }
        /**
         * 画侧栏
         */
        // 画文本:下一个
        g.setColor(Color.gray);
        g.setFont(new Font("Times", Font.BOLD, 20));
        g.drawString("下一个:", SIDEBAR_X, LEN * 2 + 10);
        // 画提示方块(下一个方块)
        g.setColor(color[nextType]);
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (SHAPE[nextType][nextState][i][j] == 1) {
                    // 填充3D矩形:fill3DRect(int x,int y,int width,int height,boolean raised)
                    g.fill3DRect(SIDEBAR_X + 20 + j * LEN, LEN * 3 + i * LEN, LEN, LEN, true);
                }
            }
        }
        // 画文本:得分
        g.setColor(Color.gray);
        g.setFont(new Font("Times", Font.BOLD, 24));
        g.drawString("等级:" + level, SIDEBAR_X, SIDEBAR_Y);
        g.drawString("得分:" + score, SIDEBAR_X, SIDEBAR_Y + 40);
        // 画文本:游戏说明
        g.setColor(Color.gray);
        g.setFont(new Font("Times", Font.BOLD, 15));
        g.drawString("玩法:", SIDEBAR_X, SIDEBAR_Y + LEN * 4);
        g.drawString("上箭头:旋转", SIDEBAR_X, SIDEBAR_Y + LEN * 5);
        g.drawString("左箭头:左移", SIDEBAR_X, SIDEBAR_Y + LEN * 6);
        g.drawString("右箭头:右移", SIDEBAR_X, SIDEBAR_Y + LEN * 7);
        g.drawString("下箭头:下落", SIDEBAR_X, SIDEBAR_Y + LEN * 8);
        g.drawString("@栗子", SIDEBAR_X, SIDEBAR_Y + LEN * 10);

        // 画当前下落块
        g.setColor(color[type]);
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (SHAPE[type][state][i][j] == 1) {
                    g.fill3DRect(MARGIN_LEFT + (x + j) * LEN, MARGIN_TOP + (y + i) * LEN, LEN, LEN, true);
                }
            }
        }

        // 画背景map
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[0].length; j++) {
                if (map[i][j] == 1) {
                    g.setColor(mapColor[i][j]);
                    g.fill3DRect(MARGIN_LEFT + j * LEN, MARGIN_TOP + i * LEN, LEN, LEN, true);
                }
            }
        }

        // 画暂停
        if (isPause) {
            g.setColor(Color.black);
            g.drawString("PAUSE", MARGIN_LEFT + LEN * 8, LEN);
        }

        // 画普通模式
        if (!isAccelMode) {
            g.setColor(Color.black);
            g.drawString("Normal Mode", LEN, LEN);
        }
        // 画加速模式
        if (isAccelMode) {
            g.setColor(Color.black);
            g.drawString("Accel Mode", LEN, LEN);
        }

    }

    /**
     * 定时器
     */
    Timer timer = new Timer(delay, new ActionListener() {
        // 定时器任务
        @Override
        public void actionPerformed(ActionEvent e) {
            down();
        }
    });

    // 暂停
    public void setPause() {
        timer.stop();
        isPause = true;
        this.repaint();
    }

    // 继续
    public void setContinue() {
        timer.restart();
        isPause = false;
        this.repaint();
    }

    // 简单模式
    public void setNormal() {
        isAccelMode = false;
        level = 0;
        timer.setDelay(1000);
        init();
    }

    // 加速模式
    public void setAccel() {
        isAccelMode = true;
        level = 1;
        timer.setDelay(500);
        init();
    }

}

以上是关于Java | Tetris的主要内容,如果未能解决你的问题,请参考以下文章

Hdu 1811 Rank of Tetris

Java项目--俄罗斯方块

HDU1760 A New Tetris Game NP态

hdu 1811 Rank of Tetris 并查集+拓扑排序

HDU 1811 Rank of Tetris(并查集+拓扑排序 非常经典)

HDU 1811 Rank of Tetris(并查集按秩合并+拓扑排序)