首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >2048图形游戏

2048图形游戏
EN

Code Review用户
提问于 2019-11-10 12:27:27
回答 3查看 506关注 0票数 8

这个游戏是为JFrame制作的。你可以用箭头来控制。单元格中的数字是数字2的度数。可以更改窗口的初始位置、窗口大小和字段中的行列数(它们相等)。您还可以更改单元格帧的宽度,在它们之间缩进。你可以调整数字2中的哪个程度才能赢。我希望你能向我指出我的代码中的弱点,如果有的话。

主要方法:

代码语言:javascript
复制
    final int windowXPos = 100;
    final int windowYPos = 100;
    final int windowWidthAndHeight = 800;
    final int spacesBetweenCells = 4;
    final int widthOfFrame = 4;
    final int inARow = 4;
    final int valueOfCellToWin = 11;

    GameWindow2048 window = new GameWindow2048(windowXPos,windowYPos, windowWidthAndHeight, spacesBetweenCells, widthOfFrame, inARow, valueOfCellToWin);

类区域骨架:

代码语言:javascript
复制
public class Area {
    Cell[][] area;
    final int valueOfCellToWin;

    public Area(int inARow, int valueOfCellToWin) {
        area = new Cell[inARow][inARow];
        fillAreaWithEmpty();
        addNewCell();
        addNewCell();
        this.valueOfCellToWin = valueOfCellToWin;
    }

    private void fillAreaWithEmpty() {
        for(int y = 0; y < area.length; y++) {
            for(int x = 0; x < area[0].length; x++) {
                area[y][x] = new Cell();
                area[y][x].setEmpty();
            }
        }
    }

    void playAgain() {
        fillAreaWithEmpty();
        addNewCell();
        addNewCell();
    }

    boolean isWin() {}

    boolean addNewCell(){}

    void makeAMove(Direction direction) {}

    private int getAmountOfEmptyCells() {}
}

全班区号:

代码语言:javascript
复制
public class Area {
    Cell[][] area;
    final int valueOfCellToWin;

    public Area(int inARow, int valueOfCellToWin) {
        area = new Cell[inARow][inARow];
        fillAreaWithEmpty();
        addNewCell();
        addNewCell();
        this.valueOfCellToWin = valueOfCellToWin;
    }

    private void fillAreaWithEmpty() {
        for(int y = 0; y < area.length; y++) {
            for(int x = 0; x < area[0].length; x++) {
                area[y][x] = new Cell();
                area[y][x].setEmpty();
            }
        }
    }

    void playAgain() {
        fillAreaWithEmpty();
        addNewCell();
        addNewCell();
    }

    boolean isWin() {
        for(int y = 0; y < area.length; y++) {
            for(int x = 0; x < area[0].length; x++) {
                if(area[y][x].getPowerOf2() == valueOfCellToWin) {
                    return true;
                }
            }
        }
        return false;
    }

    boolean addNewCell(){
        int amountOfEmptyCells = getAmountOfEmptyCells();
        int numOfCellToFill;
        int counterOfEmptyCells = 0;

        if(amountOfEmptyCells == 0) {
            return false;
        }
        numOfCellToFill = Math.abs(new Random().nextInt()%amountOfEmptyCells)+1;
        out:
        for(int y = 0; y < area.length; y++) {
            for(int x = 0; x < area[0].length; x++) {
                if(area[y][x].isEmpty()) {
                    counterOfEmptyCells++;
                    if(counterOfEmptyCells == numOfCellToFill) {
                        area[y][x].setBeginNumber();
                        break out;
                    }
                }
            }
        }
        return true;
    }

    void makeAMove(Direction direction) {
        if(direction == Direction.UP) {
            for(int x = 0; x < area[0].length; x++) {
                for(int y = 1; y <= area.length - 1; y++) {
                    if(y == 0) {
                        continue;
                    }
                    if(area[y][x].isEmpty()) {
                        continue;
                    }
                    if(area[y-1][x].isEmpty()) { //empty above - move there
                        area[y-1][x].setPowerOf2(area[y][x].getPowerOf2());
                        area[y][x].setEmpty();
                        y-=2; //as we may need to move this cell even higher.
                        continue;
                    }
                    if(area[y][x].getPowerOf2() == area[y-1][x].getPowerOf2()) { // are the same? make a merge
                        area[y][x].setEmpty();
                        area[y-1][x].increasePowerOf2();
                        // merging occurs, above the cell y-1 x there are no empty cells according to the algorithm, therefore we do not return
                    }
                }
            }
        }
        if(direction == Direction.DOWN) {
            for(int x = 0; x < area[0].length; x++) {
                for(int y = area.length - 2; y >= 0; y--) {
                    if(y == area.length - 1) {
                        continue;
                    }
                    if(area[y][x].isEmpty()) {
                        continue;
                    }
                    if(area[y+1][x].isEmpty()) {
                        area[y+1][x].setPowerOf2(area[y][x].getPowerOf2()); //empty below - move there
                        area[y][x].setEmpty();
                        y+=2; //as we may need to move this cell even lower.
                        continue;
                    }
                    if(area[y][x].getPowerOf2() == area[y+1][x].getPowerOf2()) {
                        area[y][x].setEmpty();
                        area[y+1][x].increasePowerOf2();
                        // merging occurs, below the cell y + 1 x there are no empty cells according to the algorithm, so do not return
                    }
                }
            }
        }
        if(direction == Direction.RIGHT) {
            for(int y = 0; y < area.length; y++) {
                for(int x = area[0].length - 2; x >=0; x--) {
                    if(x == area.length - 1) {
                        continue;
                    }
                    if(area[y][x].isEmpty()) {
                        continue;
                    }
                    if(area[y][x+1].isEmpty()) {
                        area[y][x+1].setPowerOf2(area[y][x].getPowerOf2());
                        area[y][x].setEmpty();
                        x+=2; //as we may need to move this cell to the right.
                        continue;
                    }
                    if(area[y][x].getPowerOf2() == area[y][x+1].getPowerOf2()) {
                        area[y][x].setEmpty();
                        area[y][x+1].increasePowerOf2();
                        //merging occurs, to the right of the cell y x+1 there are no empty cells according to the algorithm, so do not return
                    }
                }
            }
        }
        if(direction == Direction.LEFT) {
            for(int y = 0; y < area.length; y++) {
                for(int x = 1; x <= area[0].length-1; x++) {
                    if(x == 0) {
                        continue;
                    }
                    if(area[y][x].isEmpty()) {
                        continue;
                    }
                    if(area[y][x-1].isEmpty()) {
                        area[y][x-1].setPowerOf2(area[y][x].getPowerOf2());
                        area[y][x].setEmpty();
                        x-=2; // as we may need to move this cell to the left.
                        continue;
                    }
                    if(area[y][x].getPowerOf2() == area[y][x-1].getPowerOf2()) {
                        area[y][x].setEmpty();
                        area[y][x-1].increasePowerOf2();
                        //merging occurs, to the left of the cell y x-1 there are no empty cells according to the algorithm, so do not return
                    }
                }
            }
        }
    }

    private int getAmountOfEmptyCells() {
        int amountOfEmptyCells = 0;
        for(int y = 0; y < area.length; y++) {
            for(int x = 0; x < area[0].length; x++) {
                if(area[y][x].isEmpty()) {
                    amountOfEmptyCells++;
                }
            }
        }
        return amountOfEmptyCells;
    }
    Cell[][] getArea() {
        return area;
    }
}

enum GamesState完全代码:

代码语言:javascript
复制
enum GamesState{
    CONTINUETHEGAME, WIN, DEFEAT
}

类单元格完整代码:

代码语言:javascript
复制
class Cell{
    private int powerOf2; // 0 - is emptyCell

    void setBeginNumber() {
        powerOf2 = (Math.abs(new Random().nextInt()%10) != 9) ? 1 : 2;
    }
    void increasePowerOf2() {
        this.powerOf2++;
    }
    int getPowerOf2() {
        return powerOf2;
    }
    boolean isEmpty() {
        return powerOf2 == 0;
    }
    void setPowerOf2(int powerOf2) {
        this.powerOf2 = powerOf2;
    }
    void setEmpty() {
        this.powerOf2 = 0;
    }
}

类组件:

代码语言:javascript
复制
public class Component extends JPanel {
    final Cell[][] area;
    final int windowWidthAndHeight;
    final int widthOfOneCellInsideFrame;
    final int spacesBetweenCells;
    final int widthOfFrame;
    final int widthOfRectOfFrame;
    final int fontSize;
    final Font font;
    GamesState statusOfGame = GamesState.CONTINUETHEGAME;

    public Component(Cell[][] area, int windowWidthAndHeight, int spacesBetweenCells, int widthOfFrame) {
        this.area = area;
        this.windowWidthAndHeight = windowWidthAndHeight;
        this.spacesBetweenCells = spacesBetweenCells;
        this.widthOfFrame = widthOfFrame;
        widthOfOneCellInsideFrame = (windowWidthAndHeight - (2 * area.length * widthOfFrame + (area.length - 1) * spacesBetweenCells)) / area.length;
        widthOfRectOfFrame = widthOfFrame * 2 + widthOfOneCellInsideFrame;
        fontSize = widthOfOneCellInsideFrame / 3;
        font = new Font("Tahoma", Font.BOLD|Font.ITALIC, fontSize);
    }

    @Override
    public void paintComponent(Graphics gr){
        gr.clearRect(0, 0, windowWidthAndHeight, windowWidthAndHeight);
        gr.setColor(Color.lightGray);
        gr.fillRect(0,0, windowWidthAndHeight, windowWidthAndHeight);
        System.out.println(statusOfGame);
        if(statusOfGame == GamesState.CONTINUETHEGAME) {
            printArea(gr);
        } else if (statusOfGame == GamesState.WIN){
            gr.setColor(Color.BLUE);
            gr.setFont(new Font("Tahoma", Font.BOLD|Font.ITALIC, windowWidthAndHeight / 25));
            gr.drawString("U WON! If u wanna play again, press 'r'", windowWidthAndHeight/6,windowWidthAndHeight/3);
        } else {
            gr.setColor(Color.RED);
            gr.setFont(new Font("Tahoma", Font.BOLD|Font.ITALIC, windowWidthAndHeight / 25));
            gr.drawString("DEFEAT! If u wanna try again, press 'r'", windowWidthAndHeight/6,windowWidthAndHeight/3);
        }


    }
    void printArea(Graphics gr) {
        for(int y = 0, YPos; y < area.length; y++) {
            for(int x = 0, XPos; x < area[0].length; x++) {
                YPos = 2 * y * widthOfFrame;
                XPos = 2 * x * widthOfFrame;
                if(y != 0) {
                    YPos += y * (spacesBetweenCells + widthOfOneCellInsideFrame);
                }
                if(x != 0) {
                    XPos += x * (spacesBetweenCells + widthOfOneCellInsideFrame);
                }
                gr.setColor(Color.BLACK);
                gr.fillRect(XPos,YPos, widthOfRectOfFrame, widthOfRectOfFrame);
                gr.setColor(Color.BLUE);
                gr.fillRect(XPos + widthOfFrame,YPos + widthOfFrame, widthOfOneCellInsideFrame, widthOfOneCellInsideFrame);
                gr.setColor(Color.MAGENTA);
                if(!area[y][x].isEmpty()) {
                    gr.setFont(font);
                    gr.drawString(Integer.toString(area[y][x].getPowerOf2()), XPos + widthOfFrame + (widthOfOneCellInsideFrame / 2) - fontSize / 4, YPos + widthOfFrame + (widthOfOneCellInsideFrame / 2) - fontSize / 4);

                }
            }
        }
    }

    public void setGameState(GamesState statusOfGame) {
        this.statusOfGame = statusOfGame;
    }
}

具有枚举方向的GameWindow2048类:

代码语言:javascript
复制
public class GameWindow2048 extends JFrame{
    final int windowWidthAndHeight;
    final int spacesBetweenCells;
    final int widthOfFrame;
    final Area area;
    final Component component;

    Direction directionTemp;
    GamesState statusOfGame = GamesState.CONTINUETHEGAME;

    public GameWindow2048(int windowXPos, int windowYPos, int windowWidthAndHeight, int spacesBetweenCells, int widthOfFrame, int inARow, int valueOfCellToWin){ // сделать builder
        this.windowWidthAndHeight = windowWidthAndHeight;
        this.spacesBetweenCells = spacesBetweenCells;
        this.widthOfFrame = widthOfFrame;
        addKeyListener(new myKey()); 
        setFocusable(true); 
        setBounds(windowXPos,windowYPos,windowWidthAndHeight,windowWidthAndHeight + 20); //20 - калибровка
        setTitle("2048"); 
        Container container = getContentPane(); 
        area = new Area(inARow, valueOfCellToWin);
        component = new Component(area.getArea(), windowWidthAndHeight - 14, spacesBetweenCells, widthOfFrame); //14 - калибровка
        container.add(component); 
        setVisible(true); 
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public class myKey implements KeyListener{
        public void keyReleased(KeyEvent e) { //game
            if(statusOfGame == GamesState.CONTINUETHEGAME) {
                directionTemp = getDirection(e);
                if(directionTemp != null) {
                    area.makeAMove(directionTemp);
                    if(!area.addNewCell()) {
                        statusOfGame = GamesState.DEFEAT;
                        component.setGameState(statusOfGame);
                    }
                    if(area.isWin()) {
                        System.out.println("Win");
                        statusOfGame = GamesState.WIN;
                        component.setGameState(statusOfGame);
                    }
                }
                component.repaint();
            } else {
                System.out.println(e.getKeyCode());
                if(e.getKeyCode() == 82) {
                    area.playAgain();
                    statusOfGame = GamesState.CONTINUETHEGAME;
                    component.setGameState(statusOfGame);
                    component.repaint();
                }
            }

        } 




        Direction getDirection(KeyEvent e) {
            switch(e.getKeyCode()) {
                case 39:
                    return Direction.RIGHT;
                case 37:
                    return Direction.LEFT;
                case 38:
                    return Direction.UP;
                case 40:
                    return Direction.DOWN;
                default:
                    return null;
            }
        }
        public void keyPressed(KeyEvent e){}
        public void keyTyped(KeyEvent e){} 
    } 

    enum Direction{
        RIGHT, LEFT, UP, DOWN
    }
}
EN

回答 3

Code Review用户

回答已采纳

发布于 2019-11-13 11:58:39

1默认情况下,新的Cell从0开始。做一种或另一种而不是两者兼而有之:

代码语言:javascript
复制
area[y][x] = new Cell();
area[y][x].setEmpty();

没有必要继续重新计算getAmountOfEmptyCells(),您可以在Area中保留一个计数器,该计数器可以根据每个Cell进行更新。

3请将所有字段设置为private,除非它们是有意共享的(在OO语言中,您应该始终默认为私有字段)。

4不要调用类Component :)它是众所周知的现有的AWT/Swing (您的JFrame已经扩展了java.awt.Component!)。我想Area可能也是个普通的人。尝试像Board/BoardUI这样的东西,而不是Area/Component

5避免有两个来源的gameState。实际上,我认为这应该存在于Area内部。

代码语言:javascript
复制
statusOfGame = GamesState.DEFEAT;
component.setGameState(statusOfGame);

6 GameWindow2048.myKey是小写的类名!

7 GameWindow2048.myKey中的逻辑使gameState实际移动和改变,更好地定位在Area中。它是“核心”层的一部分,触发器(例如按键)应该只调用逻辑。想象一下,同一个动作有更多的触发器(在平板电脑上滑动,在桌面上鼠标拖动,声音控制,精神控制……)

8 Area应由外部构造并传递到您的UI层。这是让你保持"ui“和”核心“分开的关键原则。

代码语言:javascript
复制
area = new Area(inARow, valueOfCellToWin);

9 Direction应该移动到Area中。同样,“核心”层应该没有UI类的知识。

10 area[0].length我认为Area有一个int size;字段而不是这样做是合理的。

11

代码语言:javascript
复制
(Math.abs(new Random().nextInt()%10) != 9) ? 1 : 2;
代码语言:javascript
复制
private static final Random r = new Random();
...
powerOf2 = r.nextInt(10) == 9 ? 2 : 1; // 10% chance of a "2"

12看到这个在一个for循环中,我作为一个读者害怕!也许内环应该朝另一个方向继续推进细胞呢?

代码语言:javascript
复制
y-=2; //as we may need to move this cell even higher
票数 6
EN

Code Review用户

发布于 2019-11-11 17:11:39

我对您的代码有一些建议,对我来说,这是一个很好的点,您已经将图形部分从封装游戏逻辑的逻辑中分离出来,在CellArea两个类中。您的类Cell目前只包含一个整数值,所以您可以使用一个ints矩阵并像下面的代码那样重新定义类Area,而不是声明Cell对象的矩阵Area

代码语言:javascript
复制
public class Area {
    private int[][] area;
    private final int valueOfAreaToWin;
    private final int n;

    public Area(int n, int valueOfCellToWin) {
        this.area = new int[n][n];
        this.valueOfAreaToWin = valueOfCellToWin;
        this.n = n;
    }
}

如果您可以尝试只初始化类构造函数中的字段,而不执行其他操作。使用ints矩阵简化了类的所有方法,因为您修改了一个ints矩阵,而不是像下面的代码那样调用Cell类的方法:

代码语言:javascript
复制
private void fillAreaWithZeros() {
    for (int[] row : area) {
        Arrays.fill(row, 0);
    }
}

public int getAmountOfZeros() {
    int amount = 0;
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (area[i][j] == 0) {
                ++amount;
            }
        }
    }
    return amount;
}

public boolean isWin() {
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (area[i][j] == 0) { return false; }
        }
    }
    return true;
}

当您在矩阵元素上迭代时,尝试使用n,m来表示行数和列数,使用i和j来表示索引,因为在处理矩阵时通常会发现它们。在您的方法addNewCell中,我找到了以下代码:

out: for(int y= 0;y< area.length;y++) { for(int x= 0;x< area0.length;x++) {if(.isEmpty()){ counterOfEmptyCells++;if(counterOfEmptyCells == numOfCellToFill) { area.setBeginNumber();爆发;}

使用标签的时间是很长的,但这会使代码的可重用性复杂化,为了获得相同的行为,您可以创建一个boolean条件并在外部循环中检查它,如下所示:

代码语言:javascript
复制
boolean cond = true;
for(int i = 0; i < n && cond; ++i) {
    for(int j = 0; j < n; ++j) {
        if(area[i][j] == 0) {
            counterOfEmptyCells++;
            if(counterOfEmptyCells == numOfCellToFill) {
                int value = Math.abs(random.nextInt() % 10) != 9 ? 1 : 2;
                area[i][j] = value;
                cond = false;
                break;
            }
        }
    }
}

在内部如果cond将在断开内部for之前设置为false,则对外部for的下一次检查将发现cond等于false并将终止。

注意:正如@drekbour在下面的评论中所说,由于复合循环是该方法的最后一条指令,所以最好在内部循环中直接返回false。

票数 5
EN

Code Review用户

发布于 2019-11-12 07:55:02

一些次要问题

干-不要重复你自己,

Area.moveADirection充满了冗余代码--尝试为所有共同之处创建一个方法--我可以建议一个方法void move (int dx, int dy),它可以应用于任何方向吗?

另一个重复是迭代Area类中的单元格的代码。

代码语言:javascript
复制
for(int y = 0; y < area.length; y++) {
    for(int x = 0; x < area[0].length; x++) {
                ...
    }
}

相反,您可以为该List<Cell> getCells()提供一个方法,并使用java8流api,例如:

代码语言:javascript
复制
private int getAmountOfEmptyCells() {
    //written straight out of my head without any compiler verification
    return getCells().stream().mapToInt(c -> c.isEmpty?0:1).sum();
}

关注点分离

你的手机应该能自己画出来。如果这样做,您可以简化Component中的绘图代码:

代码语言:javascript
复制
void printArea(Graphics gr) {
    area.getCells().forEach(c -> c.draw(gr, XPos,YPos);
}

单元格将知道如何绘制自己,Cell类的新代码:

public void draw(Graphics grm int xpos, int ypos){...};

命名约定

public class myKey implements KeyListener应大写

for(int y = 0, YPos; y < area.length; y++) { YPos至少应该是yPos ..。您在哪里定义YPos??,对XPos的定义相同

复杂性

为构造函数创建一个配置对象来处理所有这些参数然后您甚至可以提供默认参数。

代码语言:javascript
复制
class Configuration {
    final int windowXPos = 100;
    final int windowYPos = 100;
    final int windowWidthAndHeight = 800;
    final int spacesBetweenCells = 4;
    final int widthOfFrame = 4;
    final int inARow = 4;
    final int valueOfCellToWin = 11;
}

这导致了

代码语言:javascript
复制
Configuration defaultConfig = new Configuration();
defaultConfig.windowXPos = 123; //example to override default values
GameWindow2048 window = new GameWindow2048(defaultConfig);

输入处理

不要直接侦听keyevent,您应该使用keybinding,请参阅javaDoc教程页

票数 5
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/232145

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档