diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..65d5733 --- /dev/null +++ b/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + org.example + ST-6 + 1.0-SNAPSHOT + + + 8 + 8 + UTF-8 + + + + + org.junit.jupiter + junit-jupiter-api + 5.10.1 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.10.1 + test + + + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + + prepare-agent + + + + generate-code-coverage-report + test + + report + + + + + + + + + + central + https://repo.maven.apache.org/maven2 + + + + \ No newline at end of file diff --git a/report/coverage.png b/report/coverage.png new file mode 100644 index 0000000..36bf9e2 Binary files /dev/null and b/report/coverage.png differ diff --git a/src/main/java/org/example/Program.java b/src/main/java/org/example/Program.java new file mode 100644 index 0000000..fe17f8e --- /dev/null +++ b/src/main/java/org/example/Program.java @@ -0,0 +1,199 @@ +package org.example; + +import java.util.Scanner; + +public class Program { + private static final char PLAYER_X = 'X'; + private static final char PLAYER_O = 'O'; + private static final char EMPTY = '-'; + private static final int SIZE = 3; + private char[][] board; + + public Program() { + board = new char[SIZE][SIZE]; + initializeBoard(); + } + + public static void main(String[] args) { + Program game = new Program(); + game.playGame(); + } + + public void initializeBoard() { + for (int i = 0; i < SIZE; i++) { + for (int j = 0; j < SIZE; j++) { + board[i][j] = EMPTY; + } + } + } + + public char[][] getBoard() { + return board; + } + + public boolean makeMove(int row, int col, char player) { + if (board[row][col] == EMPTY) { + board[row][col] = player; + return true; + } else { + return false; + } + } + + public void printBoard() { + for (int i = 0; i < SIZE; i++) { + for (int j = 0; j < SIZE; j++) { + System.out.print(board[i][j] + " "); + } + System.out.println(); + } + System.out.println(); + } + + public boolean isMovesLeft() { + for (int i = 0; i < SIZE; i++) { + for (int j = 0; j < SIZE; j++) { + if (board[i][j] == EMPTY) { + return true; + } + } + } + return false; + } + + public int evaluate() { + for (int row = 0; row < SIZE; row++) { + if (board[row][0] == board[row][1] && board[row][1] == board[row][2]) { + if (board[row][0] == PLAYER_X) return +10; + else if (board[row][0] == PLAYER_O) return -10; + } + } + + for (int col = 0; col < SIZE; col++) { + if (board[0][col] == board[1][col] && board[1][col] == board[2][col]) { + if (board[0][col] == PLAYER_X) return +10; + else if (board[0][col] == PLAYER_O) return -10; + } + } + + if (board[0][0] == board[1][1] && board[1][1] == board[2][2]) { + if (board[0][0] == PLAYER_X) return +10; + else if (board[0][0] == PLAYER_O) return -10; + } + + if (board[0][2] == board[1][1] && board[1][1] == board[2][0]) { + if (board[0][2] == PLAYER_X) return +10; + else if (board[0][2] == PLAYER_O) return -10; + } + + return 0; + } + + public int minimax(int depth, boolean isMax) { + int score = evaluate(); + + if (score == 10) return score; + + if (score == -10) return score; + + if (!isMovesLeft()) return 0; + + if (isMax) { + int best = Integer.MIN_VALUE; + + for (int i = 0; i < SIZE; i++) { + for (int j = 0; j < SIZE; j++) { + if (board[i][j] == EMPTY) { + board[i][j] = PLAYER_X; + best = Math.max(best, minimax(depth + 1, false)); + board[i][j] = EMPTY; + } + } + } + return best; + } else { + int best = Integer.MAX_VALUE; + + for (int i = 0; i < SIZE; i++) { + for (int j = 0; j < SIZE; j++) { + if (board[i][j] == EMPTY) { + board[i][j] = PLAYER_O; + best = Math.min(best, minimax(depth + 1, true)); + board[i][j] = EMPTY; + } + } + } + return best; + } + } + + public Move findBestMove() { + int bestVal = Integer.MIN_VALUE; + Move bestMove = new Move(-1, -1); + + for (int i = 0; i < SIZE; i++) { + for (int j = 0; j < SIZE; j++) { + if (board[i][j] == EMPTY) { + board[i][j] = PLAYER_X; + int moveVal = minimax(0, false); + board[i][j] = EMPTY; + + if (moveVal > bestVal) { + bestMove.row = i; + bestMove.col = j; + bestVal = moveVal; + } + } + } + } + return bestMove; + } + + public void playGame() { + Scanner scanner = new Scanner(System.in); + System.out.println("Игра Крестики-Нолики: Вы играете за 'O'."); + + while (true) { + printBoard(); + System.out.println("Ваш ход! Введите строку и столбец (0-2): "); + int row = scanner.nextInt(); + int col = scanner.nextInt(); + + if (makeMove(row, col, PLAYER_O)) { + if (evaluate() == -10) { + printBoard(); + System.out.println("Вы победили!"); + break; + } else if (!isMovesLeft()) { + printBoard(); + System.out.println("Ничья!"); + break; + } + + Move bestMove = findBestMove(); + makeMove(bestMove.row, bestMove.col, PLAYER_X); + + if (evaluate() == 10) { + printBoard(); + System.out.println("Компьютер победил!"); + break; + } else if (!isMovesLeft()) { + printBoard(); + System.out.println("Ничья!"); + break; + } + } else { + System.out.println("Недопустимый ход, попробуйте еще раз."); + } + } + } + + static class Move { + int row, col; + + Move(int row, int col) { + this.row = row; + this.col = col; + } + } +} diff --git a/src/test/java/org/example/GameTest.java b/src/test/java/org/example/GameTest.java new file mode 100644 index 0000000..51eed25 --- /dev/null +++ b/src/test/java/org/example/GameTest.java @@ -0,0 +1,135 @@ +package org.example; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ProgramTest { + private Program game; + + @BeforeEach + void setUp() { + game = new Program(); + } + + @Test + void testInitializeBoard() { + char[][] board = game.getBoard(); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + assertEquals('-', board[i][j], "Все клетки должны быть пустыми после инициализации."); + } + } + } + + @Test + void testMakeMoveValid() { + boolean move = game.makeMove(0, 0, 'X'); + assertTrue(move, "Должно быть возможным сделать ход на пустую клетку."); + assertEquals('X', game.getBoard()[0][0], "Клетка должна быть занята символом игрока."); + } + + @Test + void testMakeMoveInvalid() { + game.makeMove(0, 0, 'X'); + boolean move = game.makeMove(0, 0, 'O'); + assertFalse(move, "Невозможно сделать ход на занятую клетку."); + } + + @Test + void testEvaluateWinForX() { + game.makeMove(0, 0, 'X'); + game.makeMove(0, 1, 'X'); + game.makeMove(0, 2, 'X'); + int score = game.evaluate(); + assertEquals(10, score, "X должен выиграть, и результат должен быть +10."); + } + + @Test + void testEvaluateWinForO() { + game.makeMove(0, 0, 'O'); + game.makeMove(0, 1, 'O'); + game.makeMove(0, 2, 'O'); + int score = game.evaluate(); + assertEquals(-10, score, "O должен выиграть, и результат должен быть -10."); + } + + @Test + void testEvaluateDraw() { + game.makeMove(0, 0, 'X'); + game.makeMove(0, 1, 'O'); + game.makeMove(0, 2, 'X'); + game.makeMove(1, 0, 'O'); + game.makeMove(1, 1, 'X'); + game.makeMove(1, 2, 'O'); + game.makeMove(2, 0, 'O'); + game.makeMove(2, 1, 'X'); + game.makeMove(2, 2, 'O'); + assertFalse(game.isMovesLeft(), "Не должно оставаться свободных ходов."); + int score = game.evaluate(); + assertEquals(0, score, "Игра должна закончиться вничью."); + } + + @Test + public void testFindBestMove() { + Program game = new Program(); + game.getBoard()[0][0] = 'X'; + game.getBoard()[0][1] = 'O'; + game.getBoard()[0][2] = 'X'; + game.getBoard()[1][0] = 'O'; + game.getBoard()[1][1] = 'X'; + game.getBoard()[1][2] = 'O'; + game.getBoard()[2][0] = '-'; + game.getBoard()[2][1] = '-'; + game.getBoard()[2][2] = '-'; + + Program.Move bestMove = game.findBestMove(); + assertEquals(2, bestMove.row); + assertEquals(0, bestMove.col); + } + + @Test + void testMinimaxWinForX() { + game.makeMove(0, 0, 'X'); + game.makeMove(0, 1, 'X'); + game.makeMove(0, 2, 'X'); + int result = game.minimax(0, false); + assertEquals(10, result, "Ожидается, что минимакс вернет 10 для победы X."); + } + + @Test + void testMinimaxDraw() { + game.makeMove(0, 0, 'X'); + game.makeMove(0, 1, 'O'); + game.makeMove(0, 2, 'X'); + game.makeMove(1, 0, 'O'); + game.makeMove(1, 1, 'X'); + game.makeMove(1, 2, 'O'); + game.makeMove(2, 0, 'O'); + game.makeMove(2, 1, 'X'); + game.makeMove(2, 2, 'O'); + int result = game.minimax(0, true); + assertEquals(0, result, "Ожидается, что минимакс вернет 0 для ничьей."); + } + + @Test + void testIsMovesLeftTrue() { + assertTrue(game.isMovesLeft(), "Должны оставаться доступные ходы на пустой доске."); + } + + @Test + void testIsMovesLeftFalse() { + game.makeMove(0, 0, 'X'); + game.makeMove(0, 1, 'O'); + game.makeMove(0, 2, 'X'); + game.makeMove(1, 0, 'O'); + game.makeMove(1, 1, 'X'); + game.makeMove(1, 2, 'O'); + game.makeMove(2, 0, 'O'); + game.makeMove(2, 1, 'X'); + game.makeMove(2, 2, 'O'); + assertFalse(game.isMovesLeft(), "Не должно оставаться доступных ходов на заполненной доске."); + } +} +