Крестики-нолики Java Swing (часть 2)

Крестики-нолики Java Swing (часть 2)

Всем доброго времени суток. На связи Алексей Гулынин. В данной статье продолжаем работать над созданием игры Крестики-нолики на Java с использованием Swing. В данной статье напишем 2 класса GameSettingsForm и MainGameField. Класс GameSettingsForm является формой, в которой мы будем задавать настройки игры.

Скачать уже готовый проект полностью можно по ссылке.

Код этого класса приведен ниже:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class GameSettingsForm extends JFrame {
  GameSettingsForm gameSettingsForm = this;
  public GameSettingsForm() {
    // Далее идёт разметка формы
    setTitle("Настройки игры");
    setBounds(450, 450, 240, 190);
    setResizable(false);
    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
    JLabel jLabelMode = new JLabel("Выберете режим игры:");
    add(jLabelMode);
    JRadioButton radioButtonModeTwoPlayers = new JRadioButton("Игрок против игрока");
    add(radioButtonModeTwoPlayers);
    radioButtonModeTwoPlayers.setSelected(true);
    JRadioButton radioButtonModeAgainstAI = new JRadioButton("Игрок против компьютера");
    add(radioButtonModeAgainstAI);
    JLabel jLabelAILevel = new JLabel("Выберете уровень AI:");
    add(jLabelAILevel);
    JSlider jSlider = new JSlider(0,2,0);
    add(jSlider);
    jSlider.setAlignmentX(LEFT_ALIGNMENT);
    jSlider.setVisible(false);
    ButtonGroup buttonGroup = new ButtonGroup();
    buttonGroup.add(radioButtonModeTwoPlayers);
    buttonGroup.add(radioButtonModeAgainstAI);
    JLabel jLabelLinesCount = new JLabel("Размер поля (по умолчанию 3 на 3): ");
    add(jLabelLinesCount);
    JTextField jTextFieldLinesCount = new JTextField();
    jTextFieldLinesCount.setMaximumSize(new Dimension(100, 20));
    add(jTextFieldLinesCount);
    JButton jButtonSetSettings = new JButton("Начать игру!");
    add(jButtonSetSettings);
    setVisible(true);
    
    // Если мы выбираем режим игры против компьютера, то
    // появится слайдер, который позволяет выбрать уровень сложности
    radioButtonModeAgainstAI.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
	if (radioButtonModeAgainstAI.isSelected()) {
	  jSlider.setVisible(true);
	}
      }
     });

     // Если выбран режим игры против другого игрока, то скрываем слайдер
     radioButtonModeTwoPlayers.addActionListener(new ActionListener() {
       @Override
       public void actionPerformed(ActionEvent e) {
	if (radioButtonModeTwoPlayers.isSelected()) {
	  jSlider.setVisible(false);
	}
       }
     });

    // Задаём размер поля, если размер поля не указан, то по умолчанию он равен - 3
    jButtonSetSettings.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        MainGameField gameField = MainGameField.getInstance();
	if (jTextFieldLinesCount.getText().isEmpty()) {
	  gameField.linesCount = 3;
	}
	else {
	  try {
	    gameField.linesCount = Integer.parseInt(jTextFieldLinesCount.getText());
	  }
	  catch (NumberFormatException ex) {
	    System.out.println("Необходимо ввести целое число!");
	  }
	}
        // Запускаем игру
	gameField.startNewGame();
	if (radioButtonModeAgainstAI.isSelected()) {
	  gameField.gameMode = 2;
	}
	gameField.aiLevel = jSlider.getValue();
	gameSettingsForm.setVisible(false);
      }
    });
  }
}

Форма будет иметь следующий вид:

Крестики-нолики на Java

В классе MainGameField, который представляет собой игровое поле происходит отрисовка поля, ходов игроков. Также реализовано множество методов:

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Scanner;

public class MainGameField extends JPanel {
    // Наше игровое поле, представляет собой реализацию шаблона "Одиночка"
    private static MainGameField instance = null;
    // Размер игрового поля
    public static final int FIELD_SIZE = 450;
    // Начальное значение ячеек поля
    public final String NOT_SIGN = "*";
    // Признак, что игра закончилась
    boolean gameOver = false;
    // Сообщение, которое появится при завершении игры
    String gameOverMessage = "";
    static int linesCount = 3;
    int cellSize;
    int x;
    int y;
    // Чей ход
    boolean nextTurn = false;
    // 2 игрока
    Player player1;
    Player player2;
    // gameMode = 1 - режим игры против игрока
    // gameMode = 2 - режим игры против компьютера
    int gameMode = 1;
    // Уровень AI
    int aiLevel = 0;
    // Наше поле
    public String[][] cell;

    // Получение экземпляра MainGameField
    public static synchronized MainGameField getInstance() {
        if (instance == null)
            instance = new MainGameField();
        return instance;
    }

    // Запуск новой игры
    void startNewGame() {
        gameOver = false;
        gameOverMessage = "";
        // Размер одной ячейки
        cellSize = FIELD_SIZE / linesCount;
        cell = new String[linesCount][linesCount];
        // Перерисовка поля
        repaint();
        // Инициализация поля
        for (int i = 0; i < linesCount; i++) {
            for (int j = 0; j < linesCount; j++) {
                cell[i][j] = NOT_SIGN;
            }
        }
        gameMode = 1;
        aiLevel = 0;
        setVisible(true);
    }
    
    // Конструктор
    private MainGameField() {
        setVisible(false);
        player1 = new Player("X");
        player2 = new Player("O");

        // Считываем координаты клика мышью
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                super.mouseClicked(e);
                x = e.getX() / cellSize;
                y = e.getY() / cellSize;
                System.out.println("Mouse clicked on " + e.getX() + " " + e.getY());

                if (!gameOver) {
                    switch (gameMode) {
                        case 1: {
                            twoPlayersMode();
                            break;
                        }
                        case 2: {
                            modeAgainstAI();
                            break;
                        }
                    }
                }
            }
        });
    }

    // Режим двух игроков
    void twoPlayersMode() {
        if (player1.isShotReady == 1) {
            nextTurn = true;
            player2.isShotReady = 0;
            System.out.println("Player 1 shot!");
            player1.shot(x,y);
        }
        if (player1.win()) {
            System.out.println("Player 1 WIN!!!");
            gameOver = true;
            gameOverMessage = "Player 1 WIN!!!";
        }
        repaint();
        if (isFieldFull() && !player1.win() && !player2.win()) {
            gameOver = true;
            gameOverMessage = "DRAW!!!";
        }
        if (player2.isShotReady == 1) {
            nextTurn = false;
            player1.isShotReady = 0;
            System.out.println("Player 2 shot!");
            player2.shot(x,y);
        }
        if (!gameOver) {
            player2.shot(x, y);
        }
        if (player2.win()) {
            System.out.println("Player 2 WIN!!!");
            gameOver = true;
            gameOverMessage = "Player 2 WIN!!!";
        }
        repaint();
        if (isFieldFull() && !player2.win() && !player1.win()) {
            gameOver = true;
            gameOverMessage = "DRAW!!!";
        }
        if (nextTurn) {
            player1.isShotReady = 0;
            player2.isShotReady = 1;
        }
        else {
            player1.isShotReady = 1;
            player2.isShotReady = 0;
        }
    }
    
    // Режим против компьютера
    void modeAgainstAI() {
        Player player = new Player("X");
        AI ai = new AI("O", aiLevel, player.sign);
        if (!gameOver) {
            if (player.shot(x, y)) {
                if (player.win()) {
                    System.out.println("Player WIN!!!");
                    gameOver = true;
                    gameOverMessage = "Player WIN!!!";
                }
                if (isFieldFull()) {
                    gameOver = true;
                    gameOverMessage = "DRAW!!!";
                }
                repaint();
                if (!gameOver) {
                    ai.shot(x, y);
                }
                if (ai.win()) {
                    System.out.println("AI WIN!!!");
                    gameOver = true;
                    gameOverMessage = "AI WIN!!!";
                }
                repaint();
                if (isFieldFull() && !ai.win()) {
                    gameOver = true;
                    gameOverMessage = "DRAW!!!";
                }
            }
        }
    }

    // Проверка ячейки на занятость
    boolean isCellBusy(int x, int y) {
        if (x < 0 || y < 0 || x > linesCount - 1 || y > linesCount - 1) {
            return false;
        }
        return cell[x][y] != NOT_SIGN;
    }

    // Проверка поля на заполнение
    public boolean isFieldFull() {
        for (int i = 0; i < linesCount; i++) {
            for (int j = 0; j < linesCount; j++) {
                if (cell[i][j] == NOT_SIGN)
                    return false;
            }
        }
        return true;
    }

    // Проверяем линию на равенство значений
    public boolean checkLine(int start_x, int start_y, int dx, int dy, String sign) {
        for (int i = 0; i < linesCount; i++) {
            if (cell[start_x + i * dx][start_y + i * dy] != sign)
                return false;
        }
        return true;
    }

    // Проверка победы
    // Подробнее про эти методы рассказывается в статье, в которой мы писали
    // эту игру в процедурном стиле
    public boolean checkWin(String sign) {
        for (int i = 0; i < linesCount; i++) {
            // проверяем строки
            if (checkLine(i, 0, 0, 1, sign)) return true;
            // проверяем столбцы
            if (checkLine(0, i, 1, 0, sign)) return true;
        }
        // проверяем диагонали
        if (checkLine(0, 0, 1, 1, sign)) return true;
        if (checkLine(0, linesCount - 1, 1, -1, sign)) return true;
        return false;
    }

    // Метод, который занимается отрисовкой всей графики на форме
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        // Рисуем линии, которые представляют собой сетку
        for (int i = 0; i <= this.linesCount; i++) {
            g.drawLine(0, i * this.cellSize, FIELD_SIZE, i * this.cellSize);
            g.drawLine(i * this.cellSize, 0, i * this.cellSize, FIELD_SIZE);
        }
        for (int i = 0; i < linesCount; i++) {
            for (int j = 0; j < linesCount; j++) {
                if (cell[i][j] != NOT_SIGN) { 
                    if (cell[i][j] == "X") {
                        // Рисуем крестик
                        g.setColor(Color.RED);
                        g.drawLine((i * cellSize), (j * cellSize), (i + 1) * cellSize, (j + 1) * cellSize);
                        g.drawLine((i + 1) * cellSize, (j * cellSize), (i * cellSize), (j + 1) * cellSize);
                    }
                    if (cell[i][j] == "O") {
                        // Рисуем нолик
                        g.setColor(Color.BLUE);
                        g.drawOval((i * cellSize), (j * cellSize), cellSize, cellSize);
                    }
                }
            }
        }

        if (gameOver) {
            // Отрисовка сообщения при завершении игры
            g.setColor(Color.BLACK);
            g.fillRect(0, FIELD_SIZE / 2, FIELD_SIZE, FIELD_SIZE / 8);
            g.setColor(Color.RED);
            g.setFont(new Font("Tahoma", 10, 40));
            g.drawString(gameOverMessage, 0, 19 * FIELD_SIZE / 32);
        }
    }
}

Форма будет иметь следующий вид:

Крестики-нолики на Java

В данной статье мы закончили написание игры "Крестики-нолики" с графическим интерфейсом на Java.

На связи был Алексей Гулынин, оставляйте свои комментарии, увидимся в следующих статьях.


Комментарии:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *