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




