Крестики-нолики 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); } }); } }
Форма будет иметь следующий вид:
В классе 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.
На связи был Алексей Гулынин, оставляйте свои комментарии, увидимся в следующих статьях.