Крестики-нолики Java Swing (часть 1)
Всем доброго времени суток. На связи Алексей Гулынин. В прошлых статьях, посвященных Java, мы написали игру в процедурном стиле. В данной статье я бы хотел начать писать игру «Крестики-нолики» в объектном стиле с графическим интерфейсом, используя библиотеку Swing. Данная задача поможет разобраться в объектно-ориентированном программировании на Java.
Скачать уже готовый проект полностью можно по ссылке.
Для начала создадим абстрактный класс AGamer, от которого будут наследоваться 2 других наших класса — Player и AI:
public abstract class AGamer { // метка protected String sign; // выстрел в координаты x и y abstract boolean shot(int x, int y); // проверка победы abstract boolean win(); }
Реализуем класс игрока — Player:
public class Player extends AGamer{ // экземпляр нашего поля MainGameField gameField; // готовность к стрельбе, если = 0 // то ходит другой игрок int isShotReady = 1; // Конструктор public Player(String sign) { this.sign = sign; } // Выстрел игрока boolean shot(int x, int y) { gameField = MainGameField.getInstance(); if (!gameField.isCellBusy(x, y)) { gameField.cell[x][y] = sign; return true; } return false; } // Проверка победы boolean win() { gameField = MainGameField.getInstance(); return gameField.checkWin(this.sign); } }
Реализуем класс компьютера — AI. Здесь логика точно такая же, как и с примером в процедурном стиле. Скопирую описание данного метода из прошлой статьи:
В случае "aiLevel = 0" используется объект класса Random. В данном случае компьютер просто ходит случайным образом на любую незанятую ячейку.
В случае "aiLevel = 1" компьютер анализирует ситуацию, при которой игрок может выиграть на следующем ходу. Если такой выигрышный ход существует, то компьютер ходит на эту ячейку. Как это реализовано: в двойном цикле проверяются все ячейки, и компьютер в каждую незанятую ячейку ставит метку игрока ("field[i][j] = USER_SIGN"). Далее с помощью метода "checkWin()" проверяется является ли данный ход выигрышным, если да, то эти координаты запоминаются, и компьютер ставит на них свою метку (AI_SIGN). После каждого анализа ячейка обнуляется (("field[i][j] = NOT_SIGN").
В случае "aiLevel = 2" логика работы такая же, как и на первом уровне. Только в данном случае компьютер анализирует свой ход и, если он является выигрышным, ходит.
import java.util.Random; public class AI extends AGamer { // Экземпляр игрового поля MainGameField gameField; String playerSign = ""; // Уровень интеллекта static int aiLevel = 0; // Конструктор public AI(String sign, int aiLevel, String playerSign) { this.sign = sign; this.playerSign = playerSign; this.aiLevel = aiLevel; } // Выстрел компьютера boolean shot(int x, int y) { gameField = MainGameField.getInstance(); x = -1; y = -1; boolean ai_win = false; boolean user_win = false; // Находим выигрышный ход if (aiLevel == 2) { for (int i = 0; i < gameField.linesCount; i++) { for (int j = 0; j < gameField.linesCount; j++) { if (!gameField.isCellBusy(i, j)) { gameField.cell[i][j] = sign; if (gameField.checkWin(sign)) { x = i; y = j; ai_win = true; } gameField.cell[i][j] = gameField.NOT_SIGN; } } } } // Блокировка хода пользователя, если он побеждает на следующем ходу if (aiLevel > 0) { if (!ai_win) { for (int i = 0; i < gameField.linesCount; i++) { for (int j = 0; j < gameField.linesCount; j++) { if (!gameField.isCellBusy(i, j)) { gameField.cell[i][j] = this.playerSign; if (gameField.checkWin(this.playerSign)) { x = i; y = j; user_win = true; } gameField.cell[i][j] = gameField.NOT_SIGN; } } } } } if (!ai_win && !user_win) { do { Random rnd = new Random(); x = rnd.nextInt(gameField.linesCount); y = rnd.nextInt(gameField.linesCount); } while (gameField.isCellBusy(x, y)); } gameField.cell[x][y] = sign; return true; } boolean win() { gameField = MainGameField.getInstance(); return gameField.checkWin(this.sign); } }
Создадим класс MainForm, который будет отрисовывать основную форму:
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class MainForm extends JFrame { public MainForm() { // Заголовок формы setTitle("XO game GUI"); // Границы формы setBounds(300, 300, 455, 525); // Можно ли изменять размер формы? // в нашем случае - нет setResizable(false); // При закрытии - форма и программа закрываются setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); // Создаём экземпляр нашего игрового поля MainGameField gameField = MainGameField.getInstance(); // Создаём панель для кнопок табличного стиля JPanel buttonPanel = new JPanel(new GridLayout()); // Добавляем игровок поле в центр нашей формы add(gameField, BorderLayout.CENTER); // Панель для кнопок добавляем вниз формы add(buttonPanel, BorderLayout.SOUTH); JButton btnStart = new JButton("Начать новую игру"); JButton btnEnd = new JButton("Закончить игру"); buttonPanel.add(btnStart); buttonPanel.add(btnEnd); // Добавляем обработчик событий для закрытия формы btnEnd.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.exit(0); } }); // Добавляем обработчик событий для создания новой игры btnStart.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println(btnStart.getText()); // Загружаем новую форму (с настройками игры) GameSettingsForm gameSettingsForm = new GameSettingsForm(); } }); // Показываем форму setVisible(true); } }
Напоследок создадим основной класс MainClass, из которого мы будем запускать нашу игру:
public class MainClass { public static void main(String[] args) { MainForm gameForm = new MainForm(); } }
В следующей статье мы напишем 2 класса GameSettingsForm и MainGameField.
В данной статье мы начали писать игру "Крестики-нолики" с графическим интерфейсом.
На связи был Алексей Гулынин, оставляйте свои комментарии, увидимся в следующих статьях.