Реализация игры 2048 на java. Есть 5 классов: Main - служит для запуска приложения. Model - Содержит игровую логику. Controller - Следит за нажатием клавиш во время игры. View - Отображает все элементы на экран. Tile - Описание игрового элемента.
Проблема: Игра запускается, работает, но в момент окончания игры зацикливается всплывание диалогового окна JOptionPane.showMessageDialog(this, "You've lost :("); Всплывают окна до тех пор, пока программа аварийно не завершится. Если убрать всплытие окна, то все работает нормально. Путем многократного запуска программы выяснил, что так случается не всегда. То есть иногда всплытие окон не происходит и всплывает только одно окно, после нажатия кнопки "ОК" можно начать новую игру. Но бывает это не часто.
Предположительно некорректно работает участок кода в методе paint() в классе View:
if (isGameWon) {
JOptionPane.showMessageDialog(this, "You've won!");
} else if(isGameLost) {
JOptionPane.showMessageDialog(this, "You've lost :(");
}
Вопрос: Почему так происходит? Как это решить?
Вот код: Класс Controller:
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
/**
* Created by Павлуша on 06.02.2018.
* будет следить за нажатием клавиш во время игры.
*/
public class Controller extends KeyAdapter {
private Model model;
private View view;
private static final int WINNING_TILE = 2048;
public Controller(Model model) {
this.model = model;
view = new View(this);
}
public View getView() {
return view;
}
public Tile[][] getGameTiles() {
return model.getGameTiles();
}
public int getScore() {
return model.score;
}
public void resetGame() {
model.score = 0;
model.maxTile = 2;
view.isGameWon = false;
view.isGameLost = false;
model.resetGameTiles();
}
@Override
public void keyPressed(KeyEvent e) {
super.keyPressed(e);
if(e.getKeyCode() == KeyEvent.VK_ESCAPE) resetGame();
if(!model.canMove()) {
view.isGameLost = true;
}
if(!view.isGameLost && !view.isGameWon) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT :
model.left();
break;
case KeyEvent.VK_RIGHT :
model.right();
break;
case KeyEvent.VK_UP :
model.up();
break;
case KeyEvent.VK_DOWN :
model.down();
break;
}
}
if(WINNING_TILE == model.maxTile) view.isGameWon = true;
view.repaint();
}
}
Класс Main:
import javax.swing.*;
/**
* Created by Павлуша on 06.02.2018.
* будет содержать только метод main и служить точкой входа в наше приложение.
*/
public class Main {
public static void main(String[] args) {
Model model = new Model();
Controller controller = new Controller(model);
JFrame game = new JFrame();
game.setTitle("2048");
game.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
game.setSize(450, 500);
game.setResizable(false);
game.add(controller.getView());
game.setLocationRelativeTo(null);
game.setVisible(true);
}
}
Класс Model:
/**
* Created by Павлуша on 06.02.2018.
* будет содержать игровую логику и хранить игровое поле.
*/
public class Model {
private static final int FIELD_WIDTH = 4;
private Tile[][] gameTiles;
int score = 0;
int maxTile = 2;
public Model() {
resetGameTiles();
}
public Tile[][] getGameTiles() {
return gameTiles;
}
private void addTile() {
ArrayList<Tile> list = getEmptyTiles();
if (!list.isEmpty()) {
int indexOfAddTile = (int) ((double)list.size() * Math.random());
Tile tile = list.get(indexOfAddTile);
tile.value = Math.random() < 0.9 ? 2 : 4;
}
}
private ArrayList<Tile> getEmptyTiles() {
ArrayList<Tile> list = new ArrayList();
for (int i = 0; i < FIELD_WIDTH; i++) {
for (int j = 0; j < FIELD_WIDTH; j++) {
if(gameTiles[i][j].value == 0) list.add(gameTiles[i][j]);
}
}
return list;
}
public void resetGameTiles() {
gameTiles = new Tile[FIELD_WIDTH][FIELD_WIDTH];
for (int i = 0; i < FIELD_WIDTH; i++) {
for (int j = 0; j < FIELD_WIDTH; j++) {
gameTiles[i][j] = new Tile();
}
}
addTile();
addTile();
}
private boolean compressTiles(Tile[] tiles) {
boolean changeIsMade = false;
for (int j = 0; j < tiles.length; j++) {
for (int i = 0; i < tiles.length-1; i++) {
if(tiles[i].value == 0 && tiles[i+1].value != 0) {
swap(tiles[i], tiles[i+1]);
changeIsMade = true;
}
}
}
return changeIsMade;
}
private boolean mergeTiles(Tile[] tiles) {
boolean changeIsMade = false;
int currentTileValue;
for (int i = 0; i < tiles.length-1; i++) {
//проверка, чтобы не складывать нули
if(tiles[i].value == 0) break;
if(tiles[i].value == tiles[i+1].value) {
currentTileValue = tiles[i].value + tiles[i+1].value;
score += currentTileValue;
if(currentTileValue > maxTile) maxTile = currentTileValue;
tiles[i].value = currentTileValue;
tiles[i+1].value = 0;
i++;
changeIsMade = true;
}
}
compressTiles(tiles);
return changeIsMade;
}
private void swap(Tile tile, Tile tile1) {
int temp;
temp = tile.value;
tile.value = tile1.value;
tile1.value = temp;
}
public boolean canMove() {
if (!getEmptyTiles().isEmpty()) {
return true;
}
for (int i = 0; i < gameTiles.length; i++) {
for (int j = 0; j < gameTiles.length - 1; j++) {
if (gameTiles[i][j].value == gameTiles[i][j + 1].value) {
return true;
}
}
}
for (int j = 0; j < gameTiles.length; j++) {
for (int i = 0; i < gameTiles.length - 1; i++) {
if (gameTiles[i][j].value == gameTiles[i + 1][j].value) {
return true;
}
}
}
return false;
}
public void left() {
boolean cond = false;
for (int i = 0; i < gameTiles.length; i++) {
if (compressTiles(gameTiles[i]) || mergeTiles(gameTiles[i])) {
cond = true;
}
}
if(cond) addTile();
}
public void right() {
boolean cond = false;
rotate180();
for (int i = 0; i < gameTiles.length; i++) {
if (compressTiles(gameTiles[i]) || mergeTiles(gameTiles[i])) {
cond = true;
}
}
if(cond) addTile();
rotate180();
}
public void down() {
boolean cond = false;
rotatePlus90();
for (int i = 0; i < gameTiles.length; i++) {
if (compressTiles(gameTiles[i]) || mergeTiles(gameTiles[i])) {
cond = true;
}
}
if(cond) addTile();
rotateMinus90();
}
public void up() {
boolean cond = false;
rotateMinus90();
for (int i = 0; i < gameTiles.length; i++) {
if (compressTiles(gameTiles[i]) || mergeTiles(gameTiles[i])) {
cond = true;
}
}
if(cond) addTile();
rotatePlus90();
}
private void rotateMinus90() {
Tile[][] tempTiles = new Tile[FIELD_WIDTH][FIELD_WIDTH];
for (int i = 0; i < FIELD_WIDTH; i++) {
for (int j = 0; j < FIELD_WIDTH; j++) {
tempTiles[FIELD_WIDTH-1-j][i] = new Tile(gameTiles[i][j].value);
}
}
copyTiles(gameTiles, tempTiles);
}
private void rotate180() {
Tile[][] tempTiles = new Tile[FIELD_WIDTH][FIELD_WIDTH];
for (int i = 0; i < FIELD_WIDTH; i++) {
for (int j = 0; j < FIELD_WIDTH; j++) {
tempTiles[i][FIELD_WIDTH-1-j] = new Tile(gameTiles[i][j].value);
}
}
copyTiles(gameTiles, tempTiles);
}
private void rotatePlus90() {
Tile[][] tempTiles = new Tile[FIELD_WIDTH][FIELD_WIDTH];
for (int i = 0; i < FIELD_WIDTH; i++) {
for (int j = 0; j < FIELD_WIDTH; j++) {
tempTiles[j][FIELD_WIDTH-1-i] = new Tile(gameTiles[i][j].value);
}
}
copyTiles(gameTiles, tempTiles);
}
private void copyTiles(Tile[][] source, Tile[][] temp) {
for (int i = 0; i < FIELD_WIDTH; i++) {
for (int j = 0; j < FIELD_WIDTH; j++) {
source[i][j].value = temp[i][j].value;
}
}
}
}
Класс View:
import javax.swing.*;
import java.awt.*;
public class View extends JPanel {
private static final Color BG_COLOR = new Color(0xbbada0);
private static final String FONT_NAME = "Arial";
private static final int TILE_SIZE = 96;
private static final int TILE_MARGIN = 12;
private Controller controller;
boolean isGameWon = false;
boolean isGameLost = false;
public View(Controller controller) {
setFocusable(true);
this.controller = controller;
addKeyListener(controller);
}
@Override
public void paint(Graphics g) {
super.paint(g);
g.setColor(BG_COLOR);
g.fillRect(0, 0, this.getSize().width, this.getSize().height);
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 4; y++) {
drawTile(g, controller.getGameTiles()[y][x], x, y);
}
}
g.drawString("Score: " + controller.getScore(), 140, 465);
if (isGameWon) {
JOptionPane.showMessageDialog(this, "You've won!");
} else if(isGameLost) {
JOptionPane.showMessageDialog(this, "You've lost :(");
}
}
private void drawTile(Graphics g2, Tile tile, int x, int y) {
Graphics2D g = ((Graphics2D) g2);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int value = tile.value;
int xOffset = offsetCoors(x);
int yOffset = offsetCoors(y);
g.setColor(tile.getTileColor());
g.fillRoundRect(xOffset, yOffset, TILE_SIZE, TILE_SIZE , 8, 8);
g.setColor(tile.getFontColor());
final int size = value < 100 ? 36 : value < 1000 ? 32 : 24;
final Font font = new Font(FONT_NAME, Font.BOLD, size);
g.setFont(font);
String s = String.valueOf(value);
final FontMetrics fm = getFontMetrics(font);
final int w = fm.stringWidth(s);
final int h = -(int) fm.getLineMetrics(s, g).getBaselineOffsets()[2];
if (value != 0)
g.drawString(s, xOffset + (TILE_SIZE - w) / 2, yOffset + TILE_SIZE - (TILE_SIZE - h) / 2 - 2);
}
private static int offsetCoors(int arg) {
return arg * (TILE_MARGIN + TILE_SIZE) + TILE_MARGIN;
}
}
Класс Tile:
import java.awt.*;
/**
* Created by Павлуша on 06.02.2018.
*/
public class Tile {
int value;
public Tile() {
}
public Tile(int value) {
this.value = value;
}
public boolean isEmpty() {
return value == 0;
}
public Color getFontColor() {
if(value < 16) return new Color(0x776e65);
else return new Color(0xf9f6f2);
}
public Color getTileColor() {
Color color;
switch (value) {
case 0 :
color = new Color(0xcdc1b4);
break;
case 2 :
color = new Color(0xeee4da);
break;
case 4 :
color = new Color(0xede0c8);
break;
case 8 :
color = new Color(0xf2b179);
break;
case 16 :
color = new Color(0xf59563);
break;
case 32 :
color = new Color(0xf67c5f);
break;
case 64 :
color = new Color(0xf65e3b);
break;
case 128 :
color = new Color(0xedcf72);
break;
case 256 :
color = new Color(0xedcc61);
break;
case 512 :
color = new Color(0xedc850);
break;
case 1024 :
color = new Color(0xedc53f);
break;
case 2048 :
color = new Color(0xedc22e);
break;
default:
color = new Color(0xff0000);
break;
}
return color;
}
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
День добрыйПри запуске карты мое приложение открывает карту мира
Получил ошибку, когда мытался десериализовать ответ в Jackson