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


主要方法:
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);类区域骨架:
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() {}
}全班区号:
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完全代码:
enum GamesState{
CONTINUETHEGAME, WIN, DEFEAT
}类单元格完整代码:
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;
}
}类组件:
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类:
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
}
}发布于 2019-11-13 11:58:39
1默认情况下,新的Cell从0开始。做一种或另一种而不是两者兼而有之:
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内部。
statusOfGame = GamesState.DEFEAT;
component.setGameState(statusOfGame);6 GameWindow2048.myKey是小写的类名!
7 GameWindow2048.myKey中的逻辑使gameState实际移动和改变,更好地定位在Area中。它是“核心”层的一部分,触发器(例如按键)应该只调用逻辑。想象一下,同一个动作有更多的触发器(在平板电脑上滑动,在桌面上鼠标拖动,声音控制,精神控制……)
8 Area应由外部构造并传递到您的UI层。这是让你保持"ui“和”核心“分开的关键原则。
area = new Area(inARow, valueOfCellToWin);9 Direction应该移动到Area中。同样,“核心”层应该没有UI类的知识。
10 area[0].length我认为Area有一个int size;字段而不是这样做是合理的。
(Math.abs(new Random().nextInt()%10) != 9) ? 1 : 2;private static final Random r = new Random();
...
powerOf2 = r.nextInt(10) == 9 ? 2 : 1; // 10% chance of a "2"12看到这个在一个for循环中,我作为一个读者害怕!也许内环应该朝另一个方向继续推进细胞呢?
y-=2; //as we may need to move this cell even higher发布于 2019-11-11 17:11:39
我对您的代码有一些建议,对我来说,这是一个很好的点,您已经将图形部分从封装游戏逻辑的逻辑中分离出来,在Cell和Area两个类中。您的类Cell目前只包含一个整数值,所以您可以使用一个ints矩阵并像下面的代码那样重新定义类Area,而不是声明Cell对象的矩阵Area:
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类的方法:
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条件并在外部循环中检查它,如下所示:
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。
发布于 2019-11-12 07:55:02
一些次要问题
Area.moveADirection充满了冗余代码--尝试为所有共同之处创建一个方法--我可以建议一个方法void move (int dx, int dy),它可以应用于任何方向吗?
另一个重复是迭代Area类中的单元格的代码。
for(int y = 0; y < area.length; y++) {
for(int x = 0; x < area[0].length; x++) {
...
}
}相反,您可以为该List<Cell> getCells()提供一个方法,并使用java8流api,例如:
private int getAmountOfEmptyCells() {
//written straight out of my head without any compiler verification
return getCells().stream().mapToInt(c -> c.isEmpty?0:1).sum();
}你的手机应该能自己画出来。如果这样做,您可以简化Component中的绘图代码:
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的定义相同
为构造函数创建一个配置对象来处理所有这些参数然后您甚至可以提供默认参数。
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;
}这导致了
Configuration defaultConfig = new Configuration();
defaultConfig.windowXPos = 123; //example to override default values
GameWindow2048 window = new GameWindow2048(defaultConfig);不要直接侦听keyevent,您应该使用keybinding,请参阅javaDoc教程页
https://codereview.stackexchange.com/questions/232145
复制相似问题