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

Крестики-нолики 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.

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

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


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

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

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