前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Swing俄罗斯游戏编写详解

Swing俄罗斯游戏编写详解

作者头像
孟君
发布2019-09-03 18:15:34
2.1K0
发布2019-09-03 18:15:34
举报
文章被收录于专栏:孟君的编程札记

俄罗斯方块游戏是一个上手简单,老少皆宜的游戏,它的基本规则是移动旋转摆放游戏自动产生的各种方块,使之排列成完整的一行或多行并且消除得分。

一、你能学到什么?

通过本文的阅读,读者可以对Swing版俄罗斯方块游戏的本身,对游戏中的关键点,如图形变换、键盘事件处理、游戏进度保存、满行和消行等都会有较好的理解。

接下来我们就来看看。如何使用Swing完成一个俄罗斯方块的游戏。游戏界面有四个部分组成:

  1. 游戏面板区 -- 游戏主面板
  2. 下一个图形预览区 -- 呈现下一个图形
  3. 分数显示区 -- 显示目前的得分
  4. 菜单栏 -- 具备帮助、游戏设置、进度保存等功能

二、图形选择

一般来讲,一个图形有四个点,可以表示出常用的“一字型”,“T字型”,“Z字型”以及“L字型”方块。

如果将四个点的一个或者多个重叠,或者不采用常用的“一字型”,“T字型”,“Z字型”以及“L字型”方块,那么可以演变出更多的图形出来。如果想要更加丰富和复杂的图形,可以使用更多的点去表示想要的图形。

四个点组成的图形有如下几种:

2.1 常规图形

2.2 非重合的常规图形

如果将四个点中的一个或者多个点重合,就可以有如下几种类型的图形:

2.3 无重合点的不常规图

如果四个点不重合,还可以有如下几种比较诡异的图形,可以为游戏增加难度。

2.4 图形对象类表示

格子类( Grid.java ) ,俄罗斯方块游戏中的图形由四个格子组成。每个格子类有下x , y两个坐标位置, 颜色,以及格子图形绘制方法等。

如:

代码语言:javascript
复制
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 )。抽象类包含一些公用的属性(如:每个图形由四个方块组成)、公用的方法(如向左移动、向右移动)、抽象方法(如图形变换需要子类实现细节)。

如:

代码语言:javascript
复制
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);

}

每个子类,只要继承了该抽象父类即可拥有公有的方法和属性,实现父类定义的抽象方法就可以拥有自身的实现细节。

三、关键点

3.1 图形变换

图形变换或者图形旋转是俄罗斯方块游戏中比较关键的一个部分。 图形变换的思想?

因为一个图形有四个点来表示,可以先确定其中的一个点的变换位置然后其它的三个点根据这个确定的基点进行位置调整就可以了

比如,以一字型的图形为例,只要第二号格子为固定点,相继算出其它格子的位置即可。

代码语言:javascript
复制
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字形的方块转换:

代码语言:javascript
复制
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;
        }

    }

}

3.2 键盘事件的处理

键盘事件主要有如下几个部分

  1. 向左
  2. 向右
  3. 向下
  4. 按向上键变换图形
  5. 按空格键直接到最底部

向左

代码语言:javascript
复制
/**
     * 往左移动
     * 
     */
    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]);
            }
        }
    }

向右以及向下都和向左操作类似。

向右

代码语言:javascript
复制
 /**
     * 往右移动
     */
    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]);
            }
        }
    }

向下

代码语言:javascript
复制
 /**
     * 往下移动
     */
    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求余就可以知道怎么变换。

代码语言:javascript
复制
 /* (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()方法

代码语言:javascript
复制
case KeyEvent.VK_SPACE:
                    while (sr1.isAlive()) {
                        sr1.moveDown(flag);
                    }

针对键盘的操作,大家可以定义一个内部的事件处理器。

代码语言:javascript
复制
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创建的时候,添加上去即可。

代码语言:javascript
复制
addKeyListener(new KeyHandler());

3.3 满行及其消行操作

用一个二维数组记录当前屏幕上的方块状态,0表示没有方块,1表示有方块

满行条件?

满行的判断就归结到某一行1的个数是否等于该行列的总数,如果是就满足满行条件。

当有满行情况出现的时候,需要进行消除和计分操作。

如何消行?

消除行的一个做法就是将该行以上的行通通往下移,移动之后在将第一行的flag全部置为0

代码语言:javascript
复制
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;
                        }
                    }
                }
代码语言:javascript
复制
  /**
     * @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;

        }
    }

3.4 游戏结束判断

俄罗斯方块游戏结束的判断其实很简单,只要判断第一行的标记位是否有1即可

代码语言:javascript
复制
    /**
     * 如果是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);
            }
        }
    }
代码语言:javascript
复制
    private boolean isTopTouched() {
        for (int i = RussiaGameConstant.LEFT; i <= RussiaGameConstant.RIGHT; i++) {
            if (flag[i][RussiaGameConstant.UP] == 1) {
                return true;
            }
        }
        return false;
    }

3.5 游戏进度存储和加载

游戏进度的保存和加载功能是通过序列化反序列化来完成的。

如何保存游戏进度?

通过序列化的方式将当前游戏运行状态中用到的一些重要对象属性序列化到文件中加以保存,从而达到记录当前游戏状态的效果。

如何载入游戏进度?

通过反序列化的方式将序列化后的对象读取出来,从而达到恢复之前游戏保存时的状态的效果。用户可以在此基础上继续进行游戏。

如何载入游戏进度?

通过反序列化的方式将序列化后的对象读取出来,从而达到恢复之前游戏保存时的状态的效果。用户可以在此基础上继续进行游戏。

步骤

(一)编写保存游戏进度、加载游戏进度的事件监听器

代码语言:javascript
复制
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

代码语言:javascript
复制
  private JMenuItem loadMI = new JMenuItem("Open");

  public JMenuItem saveMI = new JMenuItem("Save");
代码语言:javascript
复制
setMenu.add(loadMI);
    setMenu.add(saveMI);
代码语言:javascript
复制
loadMI.addActionListener(new LoadAction());
saveMI.addActionListener(new SaveAction());

(三)编写具体的业务逻辑

代码语言:javascript
复制
  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);
        }
    }

3.6 游戏玩家得分排行榜

得分排行榜上列出Top 10的记录信息,包括玩家名称,得分和名次。

该功能可以通过如下几个步骤完成:

步骤

(一)创建游戏记录类和比较器

代码语言:javascript
复制
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;
    }

}

先通过分数比较,如果分数一致,则比较玩家名字。

代码语言:javascript
复制
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());
    }

}

(二)完成游戏结束后对记录文件更新的操作。

代码语言:javascript
复制
   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);
        }

    }
代码语言:javascript
复制
  /**
     * 如果是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展示出来。

代码语言:javascript
复制
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;
    }

}

四、随机产生方块

为了游戏更具随机性,随机产生方块主要包含两个部分的随机性。

4.1 方块图形产生的随机性

编写一个工厂类,随机产生方块:如产生一字型的方块、T字形的方块等。

代码语言:javascript
复制
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;
    }
}

4.2 方块初始化的随机性

每个图形通过旋转,都可以有不一样的初始化形态。比如T字形的方块就可以有四种形态,初始化的时候,也就有四种初始状态。如下是T字形方块的初始化:

代码语言:javascript
复制
    /* (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来控制。如:

代码语言:javascript
复制
public Timer timer;
    
public TimerAction timerAction;
代码语言:javascript
复制
 timerAction = new TimerAction();
        timer = new Timer(1000, timerAction);

TimeAction类执行相关的逻辑行为,如:

代码语言:javascript
复制
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游戏就实现了,本文描述和实现了三种方块类型的旋转。

  1. 田字形
  2. 一字型
  3. T字形

大家可以参考上述的变形方法完成其他类型的变化,如L型, Z字形

游戏效果如下:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-09-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟君的编程札记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二、图形选择
    • 2.1 常规图形
    • 三、关键点
      • 3.1 图形变换
        • 3.2 键盘事件的处理
          • 3.3 满行及其消行操作
            • 3.4 游戏结束判断
              • 3.5 游戏进度存储和加载
                • 如何保存游戏进度?
                • 如何载入游戏进度?
                • 如何载入游戏进度?
                • 步骤
              • 3.6 游戏玩家得分排行榜
                • 步骤
            • 四、随机产生方块
              • 4.1 方块图形产生的随机性
                • 4.2 方块初始化的随机性
                • 五、小结
                相关产品与服务
                文件存储
                文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档