package de.tims.tictactoe.ai; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import de.tims.tictactoe.GameLogic; @ExtendWith(MockitoExtension.class) class AIHardTest { static int size = 3; @Mock private GameLogic gl; @Test void emptyBoardChooseMiddleField() { char realChar = 'o'; doReturn(new char[][] { {'-', '-', '-'}, {'-', '-', '-'}, {'-', '-', '-'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(1, 1, realChar); } @Test void middleFieldAlreadySetChooseEdgeField() { char realChar = 'o'; doReturn(new char[][] { {'-', '-', '-'}, {'-', 'x', '-'}, {'-', '-', '-'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(0, 0, realChar); } @Test void opponentDidntChooseMiddleFieldSoAIDoes() { char realChar = 'o'; doReturn(new char[][] { {'-', '-', 'x'}, {'-', '-', '-'}, {'-', '-', '-'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(1, 1, realChar); } @Test void setEdgeFieldInSecondMove() { char realChar = 'o'; doReturn(new char[][] { {'x', '-', '-'}, {'-', 'o', '-'}, {'-', '-', '-'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(0, 2, realChar); } @Test void preventOpponentsWinInRow() { char realChar = 'o'; doReturn(new char[][] { {'o', '-', '-'}, {'x', 'x', '-'}, {'-', '-', '-'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(1, 2, realChar); } @Test void preventOpponentsWinInCol() { char realChar = 'o'; doReturn(new char[][] { {'o', 'x', '-'}, {'-', 'x', '-'}, {'-', '-', '-'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(2, 1, realChar); } @Test void preventOpponentsWinInDiag() { char realChar = 'o'; doReturn(new char[][] { {'x', '-', 'o'}, {'-', 'x', '-'}, {'-', '-', '-'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(2, 2, realChar); } @Test void when2InRowSetThird() { char realChar = 'o'; doReturn(new char[][] { {'o', '-', 'o'}, {'-', 'x', '-'}, {'x', 'x', '-'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(0, 1, realChar); } @Test void when2InColSetThird() { char realChar = 'o'; doReturn(new char[][] { {'o', '-', 'x'}, {'-', 'x', '-'}, {'o', '-', 'x'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(1, 0, realChar); } @Test void when2inDiagSetThird() { char realChar = 'o'; doReturn(new char[][] { {'o', '-', 'x'}, {'-', 'o', 'x'}, {'-', '-', '-'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(2, 2, realChar); } @Test void opportunityToWinIsMoreImportantThanPreventingOpponentsWinInNextRound() { char realChar = 'o'; doReturn(new char[][] { {'x', '-', 'o'}, {'-', 'o', '-'}, {'x', 'x', 'o'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(1, 2, realChar); } @Test void alwaysSetInTheNearestEdgeToOpponentsX() { char realChar = 'o'; doReturn(new char[][] { {'-', '-', '-'}, {'-', 'o', 'x'}, {'-', '-', '-'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(0, 2, realChar); } @Test void chooseFieldWhichCreatesMostOpportunitiesToWin() { char realChar = 'o'; doReturn(new char[][] { {'-', 'x', 'o'}, {'-', 'o', '-'}, {'x', '-', '-'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(1, 2, realChar); } @Test void setFirstFreeFieldIfNoBetterOpportunities() { char realChar = 'o'; doReturn(new char[][] { {'o', 'x', 'x'}, {'x', 'o', 'o'}, {'o', '-', 'x'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(2, 1, realChar); } @Test void ifRowAndColWithSameNumberContainEachContainTwoCharsDontIgnoreCol() { char realChar = 'o'; doReturn(new char[][] { {'x', 'o', 'x'}, {'-', 'o', '-'}, {'o', 'x', 'x'} }).when(gl).getBoard(); TicTacToeAI ai = new AIHard(gl); ai.calculateNextMove(); verify(gl, times(1)).setField(1, 2, realChar); } @ParameterizedTest @MethodSource("testCasesForCountCharsInRow") void countCharsInRowTest(String testName, char[][] board, int rowNum, char charToCount, int expectedResult) { doReturn(board).when(gl).getBoard(); AIHard ai = new AIHard(gl); int realResult = ai.countCharsInRow(rowNum, charToCount); assertThat(realResult).describedAs(testName).isEqualTo(expectedResult); } private static Stream testCasesForCountCharsInRow() { return Stream.of(Arguments.of("EmptyFieldReturns0", new char[][] { {'-', '-', '-'}, {'-', '-', '-'}, {'-', '-', '-'} }, 0, 'o', 0), Arguments.of("TwoCharsInRowReturnsTwo", new char[][] { {'-', '-', '-'}, {'o', 'o', '-'}, {'-', '-', '-'} }, 1, 'o', 2)); } @ParameterizedTest @MethodSource("testCasesForCountCharsInCol") void countCharsInColTest(String testName, char[][] board, int colNum, char charToCount, int expectedResult) { doReturn(board).when(gl).getBoard(); AIHard ai = new AIHard(gl); int realResult = ai.countCharsInCol(colNum, charToCount); assertThat(realResult).describedAs(testName).isEqualTo(expectedResult); } private static Stream testCasesForCountCharsInCol() { return Stream.of(Arguments.of("EmptyFieldReturns0", new char[][] { {'-', '-', '-'}, {'-', '-', '-'}, {'-', '-', '-'} }, 0, 'o', 0), Arguments.of("TwoCharsInRowReturnsTwo", new char[][] { {'-', '-', '-'}, {'o', 'o', '-'}, {'-', '-', '-'} }, 1, 'o', 1)); } @ParameterizedTest @MethodSource("testCasesForCountCharsInDiag") void countCharsInDiagTest(String testName, char[][] board, int diagNum, char charToCount, int expectedResult) { doReturn(board).when(gl).getBoard(); AIHard ai = new AIHard(gl); int realResult = ai.countCharsInDiag(diagNum, charToCount); assertThat(realResult).describedAs(testName).isEqualTo(expectedResult); } private static Stream testCasesForCountCharsInDiag() { return Stream.of(Arguments.of("EmptyFieldReturns0", new char[][] { {'-', '-', '-'}, {'-', '-', '-'}, {'-', '-', '-'} }, 0, 'o', 0), Arguments.of("TwoCharsInRowReturnsTwo", new char[][] { {'-', '-', 'o'}, {'o', 'o', '-'}, {'-', '-', '-'} }, 1, 'o', 2)); } @Test void invalidIndexCausesIndexOutOfBoundsException() { int index = 2; char charToCount = 'o'; String msg = "Only 0 and 1 are allowed values for index!"; doReturn(new char[][] { {'-', '-', '-'}, {'-', '-', '-'}, {'-', '-', '-'} }).when(gl).getBoard(); AIHard ai = new AIHard(gl); assertThatThrownBy(() -> {ai.countCharsInDiag(index, charToCount);}).isInstanceOf(IndexOutOfBoundsException.class).hasMessage(msg); } }