80 Commits

Author SHA1 Message Date
Jenkins 6669a2c78c Merge commit 'c556fe867d31adc7c9cd0c21f395f06a29a83dc8' into HEAD 3 years ago
Malte Schellhardt c556fe867d tictactoe: refactored and formatted whole code 3 years ago
Malte Schellhardt c4dcd9e4f1 tictactoe: added methods to reset internal board and gui after game over 3 years ago
Malte Schellhardt eaeb7bc48f tictactoe: refactored code to update gui 3 years ago
Malte Schellhardt 9c2d219ccc tictactoe: added gui logic 3 years ago
Malte Schellhardt 6d235a9763 tictactoe: added method to switch player 3 years ago
Malte Schellhardt 1c687f496f tictactoe: added method to get current player 3 years ago
Malte Schellhardt 0b3e24bb55 tictactoe: refactored gui playfields and related test cases 3 years ago
Malte Schellhardt 5aa5d1a1fd tictactoe: added test case to check field clicked state in gui 3 years ago
Malte Schellhardt 82890b25aa tictactoe: refactored generateGUI method 3 years ago
Malte Schellhardt 0d60a2a9f4 tictactoe: added ShowGUI class for testing new gui 3 years ago
Malte Schellhardt d6ce3f3efa tictactoe: number of fields added to the gui 3 years ago
Malte Schellhardt 435c51f3d7 tictactoe: added generateGUI method 3 years ago
Malte Schellhardt c85bc7918d tictactoe: refactored productive code 3 years ago
Malte Schellhardt d1d0aa119d tictactoe: added test case to check not yed finished game 3 years ago
Malte Schellhardt 52622fa990 tictactoe: added test case to check tied game 3 years ago
Malte Schellhardt 3936ada743 tictactoe: added test case to check end of game with win for player 2 3 years ago
Malte Schellhardt ef0aac9383 tictactoe: added test case to check end of game with win for player 1 3 years ago
Malte Schellhardt 2cb34b28d9 tictactoe: added test case to check end of game with empty board 3 years ago
Malte Schellhardt 864906fb98 tictactoe: refactoring: formatted code and removed unused lines 3 years ago
Malte Schellhardt 94a38ff70e tictactoe: added test case to check diagonal right win for player 2 3 years ago
Malte Schellhardt 85f79d14bc tictactoe: added test case to check diagonal left win for player 1 3 years ago
Malte Schellhardt 64b3f88e93 tictactoe: refactored test case and added cases to check draw 3 years ago
Malte Schellhardt 28796e3c16 tictactoe: added test case to check if player 2 has won in row 2 with full board 3 years ago
Malte Schellhardt 69d7d9c924 tictactoe: added test case to check if player 1 has won in row 1 with full board 3 years ago
Malte Schellhardt eec2f540a5 tictactoe: added test case to check if player 1 has won in row 0 with full board 3 years ago
Malte Schellhardt cdb23b1d6a tictactoe: added test case to check if player 2 has won in column 2 with full board 3 years ago
Malte Schellhardt 3ac4c2f22a tictactoe: added test case to check if player 2 has won in column 1 with full board 3 years ago
Malte Schellhardt 8e9f622172 tictactoe: added test case to check if player 1 has won in column 0 with full board 3 years ago
Malte Schellhardt 84dcefef7c tictactoe: added test case to check if player 1 has won in column 2 3 years ago
Malte Schellhardt 3e99d48ff0 tictactoe: added test case to check if player 1 has won in column 1 3 years ago
Malte Schellhardt 7bf8982378 tictactoe: added test case to check if player 2 has won in row 2 3 years ago
Malte Schellhardt 00d129f9b8 tictactoe: added test case to check if player 2 has won in row 1 3 years ago
Malte Schellhardt f81871b180 tictactoe: fixed previous checkForWin test cases 3 years ago
Malte Schellhardt 5c7908c23f tictactoe: added test case to check if player 2 has won in row 0 3 years ago
Malte Schellhardt 0d687f38e6 tictactoe: added test case for check win in row 0 3 years ago
Malte Schellhardt 54097b5c02 tictactoe: refactored test cases for checkForWin method 3 years ago
Malte Schellhardt 26768bf8f3 tictactoe: added checkForWin for special player and checking all columns 3 years ago
Malte Schellhardt 1ad993e109 tictactoe: added test case for checkForWin method 3 years ago
Malte Schellhardt 25c39d107c tictactoe: added checkForWin method 3 years ago
Malte Schellhardt aad95ece07 tictactoe: added test case for new parameterized constructor 3 years ago
Malte Schellhardt 56da63b0e6 tictactoe: added test case for trying to set a field again 3 years ago
Malte Schellhardt 85e8867e06 tictactoe: refactored fieldIsEmpty method 3 years ago
Malte Schellhardt 58f7647ce8 tictactoe: added test cases for fieldIsEmpty method and created constructor with gameboard as parameter 3 years ago
Malte Schellhardt cc16fd3389 tictactoe: added method to check if field is not set 3 years ago
Malte Schellhardt 46b1605e31 tictactoe: refactored test code to use one game instance object for all test cases 3 years ago
Malte Schellhardt 8b31b76e69 tictactoe: added test case for player 2 in playfield setter 3 years ago
Tobias Krause 332a74df86 tictactoe: bugfix: if row and col with same number each contain two chars of same player, col isnt ignored anymore 3 years ago
Tobias Krause e305236728 tictactoe: hard ai sets first empy field if theres no better option 3 years ago
Tobias Krause 412558578c tictactoe: hard ai sets always in the nearest edge to opponents field 3 years ago
Tobias Krause f44e9e6eb2 tictactoe: hard ai sets always in the nearest edge to opponents field 3 years ago
Tobias Krause 9111545ec3 tictactoe: hard ai checks for own opportunity to win first 3 years ago
Tobias Krause c0310d8ed6 tictactoe: hard ai uses chance to win in diagonal 3 years ago
Tobias Krause 7f1b801c93 tictactoe: hard ai uses chance to win in cols 3 years ago
Tobias Krause 360870072e tictactoe: hard ai uses chance to win in row 3 years ago
Tobias Krause b29b7ee9d7 tictactoe: hard AI prevents opponents win in diagonal 3 years ago
Tobias Krause 7115050d60 tictactoe: hard AI prevents opponents win in col 3 years ago
Tobias Krause c6e0d83d70 tictactoe: hard AI prevents opponents win in row 3 years ago
Tobias Krause 01f94e3198 tictactoe: countCharsInDiag throws IndexOutOfBoundsException for invalid index 3 years ago
Tobias Krause cce28aacd0 tictactoe: countCharsInDiag counts number of given char in specific diagonal 3 years ago
Tobias Krause 525754409a tictactoe: added method countCharsInDiag 3 years ago
Tobias Krause 36b8cdfcac tictactoe: countCharsInCol counts number of given char in specific col 3 years ago
Tobias Krause 8c89d0edd6 tictactoe: added method countCharsInCol 3 years ago
Tobias Krause 9029de6c12 tictactoe: countCharsInRow counts number of given char in specific row 3 years ago
Tobias Krause ddf9aebb8f tictactoe: added method countCharsInRow 3 years ago
Tobias Krause 4456251b07 tictactoe: hard AI sets edge in second move 3 years ago
Tobias Krause 7c7e134ad9 tictactoe: hard AI sets middle field if it hasnt been set yet 3 years ago
Tobias Krause 93591f47c0 tictactoe: hard AI chooses edge field if field in the middle was already set 3 years ago
Tobias Krause 88d559c6c9 tictactoe: added hard AI 3 years ago
Tobias Krause bd561b7024 tictactoe: AIEasy never sets a field which was already set 3 years ago
Tobias Krause a786eb7496 tictactoe: added easy AI 3 years ago
Malte Schellhardt 972fcd97a6 tictactoe: added setter for playfield and changed internal board datatype to char 3 years ago
Malte Schellhardt 27cadc4097 tictactoe: refactored test code 3 years ago
Malte Schellhardt 4e425ff54b tictactoe: added restriction for board sizes below three 3 years ago
Malte Schellhardt edbbefc7ff tictactoe: fixed fieldCountTest. Now works with different playfield sizes 3 years ago
Malte Schellhardt 60ec15241b tictactoe: refactored board management and added constructor to handle different playfield sizes 3 years ago
Malte Schellhardt 068bed8357 tictactoe: added method to count playing fields 3 years ago
Malte Schellhardt d6b0153491 tictactoe: added getter for game board 3 years ago
Malte Schellhardt 4e743e694e tictactoe: added class for gamelogic 3 years ago
Lorenz Hohmann 2c299f689a Added GameManager and check if GameState is correct after start function call 3 years ago
  1. 183
      src/main/java/de/tims/tictactoe/GameLogic.java
  2. 22
      src/main/java/de/tims/tictactoe/ShowGUI.java
  3. 34
      src/main/java/de/tims/tictactoe/ai/AIEasy.java
  4. 190
      src/main/java/de/tims/tictactoe/ai/AIHard.java
  5. 5
      src/main/java/de/tims/tictactoe/ai/TicTacToeAI.java
  6. 337
      src/test/java/de/tims/tictactoe/GameLogicTest.java
  7. 62
      src/test/java/de/tims/tictactoe/ai/AIEasyTest.java
  8. 262
      src/test/java/de/tims/tictactoe/ai/AIHardTest.java

183
src/main/java/de/tims/tictactoe/GameLogic.java

@ -0,0 +1,183 @@
package de.tims.tictactoe;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class GameLogic implements ActionListener {
private static final char EMPTY_FIELD = '-';
private static final char PLAYER_1 = 'x';
private static final char PLAYER_2 = 'o';
private char[][] board;
private final char[] occupiedFields = { PLAYER_1, PLAYER_2 };
private char currentPlayer = PLAYER_1;
private boolean gui = false;
private JButton[][] fields;
private JPanel contentPanel;
public GameLogic(int size) {
if (size < 3) {
size = 3;
}
this.board = new char[size][size];
this.resetBoard();
}
public GameLogic(char[][] board) {
this.board = board;
}
public char[][] getBoard() {
return this.board;
}
public int countFields() {
return this.board[0].length * this.board.length;
}
public void setField(int column, int row, char player) {
if (this.fieldIsEmpty(column, row)) {
this.board[column][row] = player;
if (gui) {
this.fields[column][row].setText("" + this.getCurrentPlayer());
this.updateGUI();
}
}
}
public boolean fieldIsEmpty(int column, int row) {
for (char field : this.occupiedFields) {
if (this.board[column][row] == field)
return false;
}
return true;
}
public boolean checkForWin(char player) {
boolean won = false;
int countFields = 0;
// check columns
for (int i = 0; i < this.board.length; i++) {
for (int j = 0; j < this.board[0].length; j++) {
if (this.board[j][i] == player)
countFields++;
}
if (countFields == this.board.length)
return won = true;
countFields = 0;
}
// check rows
for (int i = 0; i < this.board[0].length; i++) {
for (int j = 0; j < this.board.length; j++) {
if (this.board[i][j] == player)
countFields++;
}
if (countFields == this.board.length)
return won = true;
countFields = 0;
}
// check diagonal left
for (int i = this.board.length - 1, j = this.board.length - 1; i >= 0; i--, j--) {
if (this.board[i][j] == player)
countFields++;
}
if (countFields == this.board.length)
return won = true;
countFields = 0;
// check diagonal right
for (int i = this.board.length - 1, j = 0; i >= 0; i--, j++) {
if (this.board[i][j] == player)
countFields++;
}
if (countFields == this.board.length)
return won = true;
return won;
}
public boolean checkEndOfGame() {
return this.checkForWin(PLAYER_1) || this.checkForWin(PLAYER_2);
}
public char getCurrentPlayer() {
return this.currentPlayer;
}
public void switchPlayer() {
this.currentPlayer = this.currentPlayer == PLAYER_1 ? PLAYER_2 : PLAYER_1;
}
public void resetBoard() {
for (int i = 0; i < this.board.length; i++) {
for (int j = 0; j < this.board.length; j++) {
this.board[i][j] = EMPTY_FIELD;
}
}
}
public JPanel generateGUI() {
this.fields = new JButton[this.board.length][this.board.length];
this.contentPanel = new JPanel();
this.contentPanel.setLayout(new GridLayout(this.board.length, this.board.length));
for (int i = 0; i < this.fields.length; i++) {
for (int j = 0; j < this.fields.length; j++) {
this.fields[i][j] = new JButton();
this.fields[i][j].addActionListener(this);
this.contentPanel.add(this.fields[i][j]);
}
}
this.gui = true;
return this.contentPanel;
}
public JButton getGUIField(int column, int row) {
return this.fields[column][row];
}
private void updateGUI() {
if (this.checkEndOfGame()) {
for (int i = 0; i < this.fields.length; i++) {
for (int j = 0; j < this.fields.length; j++) {
this.fields[i][j].setEnabled(false);
}
}
JOptionPane.showMessageDialog(contentPanel, "Spieler " + this.currentPlayer + " hat gewonnen.");
this.resetGUI();
}
this.switchPlayer();
}
private void resetGUI() {
for (int i = 0; i < this.fields.length; i++) {
for (int j = 0; j < this.fields.length; j++) {
this.resetBoard();
this.fields[i][j].setText("");
this.fields[i][j].setEnabled(true);
}
}
this.switchPlayer();
}
@Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < this.fields.length; i++) {
for (int j = 0; j < this.fields[0].length; j++) {
if (e.getSource() == this.fields[i][j]) {
this.setField(i, j, currentPlayer);
this.fields[i][j].getText();
}
}
}
}
}

22
src/main/java/de/tims/tictactoe/ShowGUI.java

@ -0,0 +1,22 @@
package de.tims.tictactoe;
import javax.swing.JFrame;
public class ShowGUI {
private JFrame frame;
public ShowGUI() {
this.frame = new JFrame("TicTacToe");
this.frame.setSize(600, 600);
GameLogic game = new GameLogic(3);
this.frame.add(game.generateGUI());
this.frame.setVisible(true);
}
public static void main(String[] args) {
new ShowGUI();
}
}

34
src/main/java/de/tims/tictactoe/ai/AIEasy.java

@ -0,0 +1,34 @@
package de.tims.tictactoe.ai;
import de.tims.tictactoe.GameLogic;
import java.util.Random;
public class AIEasy implements TicTacToeAI {
private static final char AI_CHAR = 'o';
private static final char EMPTY_CHAR = '-';
private Random rand;
private GameLogic gl;
private int boardSize;
public AIEasy(GameLogic gl) {
this.gl = gl;
boardSize = gl.getBoard().length;
rand = new Random();
}
@Override
public void calculateNextMove() {
char[][] board = gl.getBoard();
int row;
int col;
do {
row = rand.nextInt(boardSize);
col = rand.nextInt(boardSize);
} while (board[row][col] != EMPTY_CHAR);
gl.setField(row, col, AI_CHAR);
}
}

190
src/main/java/de/tims/tictactoe/ai/AIHard.java

@ -0,0 +1,190 @@
package de.tims.tictactoe.ai;
import de.tims.tictactoe.GameLogic;
public class AIHard implements TicTacToeAI {
private static final char AI_CHAR = 'o';
private static final char EMPTY_CHAR = '-';
private static final char PLAYER_CHAR = 'x';
private static final int BOARD_SIZE = 3;
private GameLogic gl;
public AIHard(GameLogic gl) throws IllegalArgumentException {
if (gl.getBoard().length != BOARD_SIZE) {
throw new IllegalArgumentException("Hard AI only supports 3x3 boards!");
}
this.gl = gl;
}
@Override
public void calculateNextMove() {
char[][] board = gl.getBoard();
int row;
int col;
int charsInRow = 0;
int charsInCol = 0;
int charsInDiag = 0;
char actualChar;
for (int i = 0; i < 2; i++) {
actualChar = (i == 0) ? AI_CHAR : PLAYER_CHAR;
for (int j = 0; j < BOARD_SIZE; j++) {
charsInRow = countCharsInRow(j, actualChar);
charsInCol = countCharsInCol(j, actualChar);
if (j < 2) {
charsInDiag = countCharsInDiag(j, actualChar);
if (charsInDiag == BOARD_SIZE - 1) {
for (int k = 0; k < BOARD_SIZE; k++) {
row = k;
col = (j == 0) ? k : BOARD_SIZE - 1 - k;
if (board[row][col] == EMPTY_CHAR) {
gl.setField(row, col, AI_CHAR);
return;
}
}
}
}
if (charsInRow == BOARD_SIZE - 1 || charsInCol == BOARD_SIZE - 1) {
for (int k = 0; k < BOARD_SIZE; k++) {
if (charsInRow == BOARD_SIZE - 1) {
row = j;
col = k;
if (board[row][col] == EMPTY_CHAR) {
gl.setField(row, col, AI_CHAR);
return;
}
}
if (charsInCol == BOARD_SIZE - 1) {
row = k;
col = j;
if (board[row][col] == EMPTY_CHAR) {
gl.setField(row, col, AI_CHAR);
return;
}
}
}
}
}
}
if (board[BOARD_SIZE / 2][BOARD_SIZE / 2] == EMPTY_CHAR) {
gl.setField(BOARD_SIZE / 2, BOARD_SIZE / 2, AI_CHAR);
return;
} else if (board[BOARD_SIZE / 2][BOARD_SIZE / 2] == AI_CHAR && (board[0][0] == AI_CHAR || board[0][BOARD_SIZE - 1] == AI_CHAR || board[BOARD_SIZE - 1][0] == AI_CHAR || board[BOARD_SIZE - 1][BOARD_SIZE - 1] == AI_CHAR)) {
int onwCharsInRow = 0;
int ownCharsInCol = 0;
int emptyCharsInRow = 0;
int emptyCharsInCol = 0;
for (int i = BOARD_SIZE - 2; i > 0; i--) {
for (int j = 0; j < BOARD_SIZE; j += BOARD_SIZE - 1) {
for (int k = 1; k < BOARD_SIZE - 1; k++) {
for (int l = 0; l < 2; l++) {
row = (l == 0) ? j : k;
col = (l == 0) ? k : j;
onwCharsInRow = countCharsInRow(row, AI_CHAR);
ownCharsInCol = countCharsInCol(col, AI_CHAR);
emptyCharsInRow = countCharsInRow(row, EMPTY_CHAR);
emptyCharsInCol = countCharsInCol(col, EMPTY_CHAR);
if (onwCharsInRow >= i && ownCharsInCol >= i && emptyCharsInRow >= BOARD_SIZE - onwCharsInRow && emptyCharsInCol == BOARD_SIZE - ownCharsInCol) {
gl.setField(row, col, AI_CHAR);
return;
}
}
}
}
}
} else {
boolean emptyEdgeFound = false;
row = -1;
col = -1;
int prioRow = -1;
int prioCol = -1;
for (int i = 0; i < BOARD_SIZE; i = i + BOARD_SIZE - 1) {
for (int j = 0; j < BOARD_SIZE; j = j + BOARD_SIZE - 1) {
if (board[i][j] == EMPTY_CHAR) {
row = (row == -1) ? i : row;
col = (col == -1) ? j : col;
if (countCharsInRow(i, PLAYER_CHAR) != 0 || countCharsInCol(j, PLAYER_CHAR) != 0) {
prioRow = i;
prioCol = j;
emptyEdgeFound = true;
break;
}
}
}
if (emptyEdgeFound) {
break;
}
}
if (row != -1 && col != -1) {
row = (prioRow != -1) ? prioRow : row;
col = (prioCol != -1) ? prioCol : col;
gl.setField(row, col, AI_CHAR);
return;
}
}
for (row = 0; row < BOARD_SIZE; row++) {
for (col = 0; col < BOARD_SIZE; col++) {
if (board[row][col] == EMPTY_CHAR) {
gl.setField(row, col, AI_CHAR);
return;
}
}
}
}
public int countCharsInRow(int index, char charToCount) {
int count = 0;
char[][] board = gl.getBoard();
for (int i = 0; i < BOARD_SIZE; i++) {
count += (board[index][i] == charToCount) ? 1 : 0;
}
return count;
}
public int countCharsInCol(int index, char charToCount) {
int count = 0;
char[][] board = gl.getBoard();
for (int i = 0; i < BOARD_SIZE; i++) {
count += (board[i][index] == charToCount) ? 1 : 0;
}
return count;
}
public int countCharsInDiag(int index, char charToCount) throws IndexOutOfBoundsException {
if (index < 0 || index > 1) {
throw new IndexOutOfBoundsException("Only 0 and 1 are allowed values for index!");
}
int count = 0;
char[][] board = gl.getBoard();
for (int i = 0; i < BOARD_SIZE; i++) {
count += (board[i][(index == 0) ? i : BOARD_SIZE - 1 - i] == charToCount) ? 1 : 0;
}
return count;
}
}

5
src/main/java/de/tims/tictactoe/ai/TicTacToeAI.java

@ -0,0 +1,5 @@
package de.tims.tictactoe.ai;
public interface TicTacToeAI {
void calculateNextMove();
}

337
src/test/java/de/tims/tictactoe/GameLogicTest.java

@ -0,0 +1,337 @@
package de.tims.tictactoe;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.awt.Component;
import java.util.stream.Stream;
import javax.swing.JButton;
import javax.swing.JPanel;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@TestInstance(Lifecycle.PER_CLASS)
class GameLogicTest {
private final int SIZE = 3;
private GameLogic game;
@BeforeAll
void setUpBeforeClass() throws Exception {
this.game = new GameLogic(SIZE);
}
@Test
void createGameLogicTest() {
GameLogic expectedResult = this.game;
GameLogic realResult = new GameLogic(SIZE);
assertEquals(expectedResult.getClass(), realResult.getClass());
}
@Test
void getBoardTest() {
// @formatter:off
char[][] expectedResult = new char[][]{{'-', '-', '-'},
{'-', '-', '-'},
{'-', '-', '-'}};
// @formatter:on
char[][] realResult = this.game.getBoard();
assertArrayEquals(expectedResult, realResult);
}
@Test
void createGameLogicWithGivenBoardTest() {
// @formatter:off
char[][] expectedResult = new char[][]{{'x', '-', '-'},
{'-', 'o', '-'},
{'x', '-', '-'}};
// @formatter:on
char[][] givenBoard = expectedResult;
char[][] realResult = new GameLogic(givenBoard).getBoard();
assertArrayEquals(expectedResult, realResult);
}
@Test
void generateGUITest() {
JPanel expectedResult = new JPanel();
JPanel realResult = this.game.generateGUI();
assertEquals(expectedResult.getClass(), realResult.getClass());
}
@Test
void numberOfGUIFieldsTest() {
int expectedResult = (int) Math.pow(SIZE, 2);
int realResult = 0;
JPanel gui = this.game.generateGUI();
Component[] components = gui.getComponents();
for (Component component : components) {
if (component instanceof JButton)
realResult++;
}
assertEquals(expectedResult, realResult);
}
@Test
void getCurrentPlayerTest() {
GameLogic game = new GameLogic(SIZE);
char expectedResult = 'x';
char realResult = game.getCurrentPlayer();
assertEquals(expectedResult, realResult);
}
@Test
void switchPlayerTest() {
GameLogic game = new GameLogic(SIZE);
game.switchPlayer();
char expectedResult = 'o';
char realResult = game.getCurrentPlayer();
assertEquals(expectedResult, realResult);
}
@Test
void resetBoardTest() {
GameLogic game = new GameLogic(SIZE);
game.setField(1, 2, 'x');
// @formatter:off
char[][] expectedResult = new char[][]{{'-', '-', '-'},
{'-', '-', '-'},
{'-', '-', '-'}};
// @formatter:on
game.resetBoard();
char[][] realResult = game.getBoard();
assertArrayEquals(expectedResult, realResult);
}
@ParameterizedTest(name = "[{index}] {0} -> {2} fields")
@MethodSource("testCasesForCountPlayfields")
void fieldCountTest(String testName, int size, int expectedResult) {
GameLogic game = new GameLogic(size);
int realResult = game.countFields();
assertEquals(expectedResult, realResult);
}
@ParameterizedTest(name = "[{index}] {0}")
@MethodSource("testCasesForSetField")
void setFieldTest(String testName, int column, int row, char player, char[][] expectedResult) {
this.game.setField(column, row, player);
char[][] realResult = this.game.getBoard();
assertArrayEquals(expectedResult, realResult);
}
@ParameterizedTest(name = "[{index}] {0}")
@MethodSource("testCasesForCheckEmptyField")
void fieldIsEmptyTest(String testName, int columnToCheck, int rowToCheck, boolean expectedResult, char[][] board) {
GameLogic game = new GameLogic(board);
boolean realResult = game.fieldIsEmpty(columnToCheck, rowToCheck);
assertEquals(expectedResult, realResult);
}
@ParameterizedTest(name = "[{index}] {0}: should be {2}")
@MethodSource("testCasesForCheckForWin")
void checkForWinTest(String testName, char player, boolean expectedResult, char[][] boardToCheck) {
boolean realResult = new GameLogic(boardToCheck).checkForWin(player);
assertEquals(expectedResult, realResult);
}
@ParameterizedTest(name = "[{index}] {0}: should be {1}")
@MethodSource("testCasesForCheckEndOfGame")
void checkEndOfGameTest(String testName, boolean expectedResult, char[][] boardToCheck) {
boolean realResult = new GameLogic(boardToCheck).checkEndOfGame();
assertEquals(expectedResult, realResult);
}
@ParameterizedTest(name = "[{index}] {0}: should be {1}")
@MethodSource("testCasesForCheckButtonState")
void buttonStateTest(String testName, boolean expectedResult, boolean doClick, int column, int row) throws InterruptedException {
GameLogic game = new GameLogic(SIZE);
game.generateGUI();
JButton currentField = game.getGUIField(0, 0);
if (doClick)
currentField.doClick();
boolean realResult = !currentField.getText().isEmpty();
assertEquals(expectedResult, realResult);
}
// @formatter:off
private static Stream<Arguments> testCasesForCountPlayfields() {
return Stream.of(Arguments.of("1x1 board with too few fields", 1, 9),
Arguments.of("2x2 board with too few fields", 2, 9),
Arguments.of("3x3 board with 9 playfields", 3, 9),
Arguments.of("4x4 board with 16 playfields", 4, 16),
Arguments.of("5x5 board with 25 playfields", 5, 25));
}
private static Stream<Arguments> testCasesForSetField() {
return Stream.of(
Arguments.of("set field [0][0] for player 1", 0, 0, 'x', new char[][]
{{'x', '-', '-'},
{'-', '-', '-'},
{'-', '-', '-'}}),
Arguments.of("set field [1][0] for player 2", 1, 0, 'o', new char[][]
{{'x', '-', '-'},
{'o', '-', '-'},
{'-', '-', '-'}}),
Arguments.of("try to set occupied field [1][0] for player 1", 1, 0, 'x', new char[][]
{{'x', '-', '-'},
{'o', '-', '-'},
{'-', '-', '-'}})
);
}
private static Stream<Arguments> testCasesForCheckEmptyField() {
return Stream.of(
Arguments.of("check an empty field", 0, 0, true, new char[][]
{{'-', '-', '-'},
{'-', '-', '-'},
{'-', '-', '-'}}),
Arguments.of("check a field set by player 1", 0, 0, false, new char[][]
{{'x', '-', '-'},
{'-', '-', '-'},
{'-', '-', '-'}}),
Arguments.of("check a field set by player 2", 0, 0, false, new char[][]
{{'o', '-', '-'},
{'-', '-', '-'},
{'-', '-', '-'}})
);
}
private static Stream<Arguments> testCasesForCheckForWin() {
return Stream.of(
Arguments.of("check win in column 0 for player 1", 'x', true, new char[][]
{{'x', '-', '-'},
{'x', '-', '-'},
{'x', '-', '-'}}),
Arguments.of("check win in column 1 for player 1", 'x', true, new char[][]
{{'-', 'x', '-'},
{'-', 'x', '-'},
{'-', 'x', '-'}}),
Arguments.of("check win in column 2 for player 1", 'x', true, new char[][]
{{'-', '-', 'x'},
{'-', '-', 'x'},
{'-', '-', 'x'}}),
Arguments.of("check win in column 0 for player 2", 'o', true, new char[][]
{{'o', '-', '-'},
{'o', '-', '-'},
{'o', '-', '-'}}),
Arguments.of("check win in row 0 for player 1", 'x', true, new char[][]
{{'x', 'x', 'x'},
{'-', '-', '-'},
{'-', '-', '-'}}),
Arguments.of("check win in row 0 for player 2", 'o', true, new char[][]
{{'o', 'o', 'o'},
{'-', '-', '-'},
{'-', '-', '-'}}),
Arguments.of("check win in row 1 for player 2", 'o', true, new char[][]
{{'-', '-', '-'},
{'o', 'o', 'o'},
{'-', '-', '-'}}),
Arguments.of("check win in row 2 for player 2", 'o', true, new char[][]
{{'-', '-', '-'},
{'-', '-', '-'},
{'o', 'o', 'o'}}),
Arguments.of("check win in column 0 for player 1 with full board", 'x', true, new char[][]
{{'x', 'o', 'o'},
{'x', 'o', 'x'},
{'x', 'x', 'o'}}),
Arguments.of("check win in column 1 for player 2 with full board", 'o', true, new char[][]
{{'x', 'o', 'o'},
{'x', 'o', 'x'},
{'o', 'o', 'x'}}),
Arguments.of("check win in column 2 for player 2 with full board", 'o', true, new char[][]
{{'x', 'o', 'o'},
{'x', 'x', 'o'},
{'o', 'o', 'o'}}),
Arguments.of("check win in row 0 for player 1 with full board", 'x', true, new char[][]
{{'x', 'x', 'x'},
{'x', 'o', 'o'},
{'o', 'o', 'x'}}),
Arguments.of("check win in row 1 for player 1 with full board", 'x', true, new char[][]
{{'x', 'x', 'o'},
{'x', 'x', 'x'},
{'o', 'o', 'x'}}),
Arguments.of("check win in row 2 for player 2 with full board", 'o', true, new char[][]
{{'x', 'x', 'o'},
{'o', 'x', 'x'},
{'o', 'o', 'o'}}),
Arguments.of("check win in column 0 for player 2", 'o', false, new char[][]
{{'o', '-', '-'},
{'o', '-', '-'},
{'-', '-', '-'}}),
Arguments.of("check a draw for player 2", 'o', false, new char[][]
{{'o', 'o', 'x'},
{'o', 'x', 'x'},
{'x', 'x', 'o'}}),
Arguments.of("check a draw for player 1", 'x', false, new char[][]
{{'o', 'o', 'x'},
{'o', 'o', 'x'},
{'x', 'x', 'o'}}),
Arguments.of("check diagonal left win for player 1", 'x', true, new char[][]
{{'x', 'o', 'x'},
{'x', 'x', 'o'},
{'o', 'o', 'x'}}),
Arguments.of("check diagonal right win for player 2", 'o', true, new char[][]
{{'x', 'x', 'o'},
{'x', 'o', 'o'},
{'o', 'x', 'x'}})
);
}
private static Stream<Arguments> testCasesForCheckEndOfGame() {
return Stream.of(
Arguments.of("check empty board", false, new char[][]
{{'-', '-', '-'},
{'-', '-', '-'},
{'-', '-', '-'}}),
Arguments.of("end of game with win for player 1", true, new char[][]
{{'x', 'o', 'x'},
{'x', 'x', 'o'},
{'x', 'o', 'o'}}),
Arguments.of("end of game with win for player 2", true, new char[][]
{{'x', 'x', 'o'},
{'o', 'o', 'o'},
{'x', 'o', 'x'}}),
Arguments.of("check tied game", true, new char[][]
{{'x', 'x', 'o'},
{'o', 'x', 'o'},
{'x', 'o', 'x'}}),
Arguments.of("check not yet finished game", false, new char[][]
{{'x', 'x', '-'},
{'o', 'o', '-'},
{'x', 'o', 'x'}})
);
}
private static Stream<Arguments> testCasesForCheckButtonState() {
return Stream.of(
Arguments.of("trigger gui field [0][0]", true, true, 0, 0),
Arguments.of("dont't trigger gui field [1][1]", false, false, 1, 1)
);
}
// @formatter:on
}

62
src/test/java/de/tims/tictactoe/ai/AIEasyTest.java

@ -0,0 +1,62 @@
package de.tims.tictactoe.ai;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import de.tims.tictactoe.GameLogic;
@ExtendWith(MockitoExtension.class)
class AIEasyTest {
static int size = 3;
@Mock
private GameLogic gl;
@Test
void emptyBoardChooseRandomField() {
char realChar = 'o';
doReturn(new char[][] { {'-', '-', '-'}, {'-', '-', '-'}, {'-', '-', '-'} }).when(gl).getBoard();
TicTacToeAI ai = new AIEasy(gl);
//run method 100 times, because of random generator
for (int i = 0; i < 100; i++) {
ai.calculateNextMove();
}
verify(gl, times(100)).setField(intThat(new ChooseRandomFieldMatcher()), intThat(new ChooseRandomFieldMatcher()), eq(realChar));
}
@Test
void notEmptyBoardChooseRandomFreeField() {
char realChar = 'o';
doReturn(new char[][] { {'x', '-', 'o'}, {'-', 'o', '-'}, {'-', 'x', 'x'} }).when(gl).getBoard();
TicTacToeAI ai = new AIEasy(gl);
//run method 100 times, because of random generator
for (int i = 0; i < 100; i++) {
ai.calculateNextMove();
}
verify(gl, times(100)).setField(intThat(new ChooseRandomFieldMatcher()), intThat(new ChooseRandomFieldMatcher()), eq(realChar));
//verify that the method is never called with a field which was already set
verify(gl, never()).setField(0, 0, realChar);
verify(gl, never()).setField(0, 2, realChar);
verify(gl, never()).setField(1, 1, realChar);
verify(gl, never()).setField(2, 1, realChar);
verify(gl, never()).setField(2, 2, realChar);
}
private static class ChooseRandomFieldMatcher implements ArgumentMatcher<Integer> {
@Override
public boolean matches(Integer argument) {
return argument.intValue() >= 0 && argument.intValue() < size;
}
}
}

262
src/test/java/de/tims/tictactoe/ai/AIHardTest.java

@ -0,0 +1,262 @@
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<Arguments> 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<Arguments> 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<Arguments> 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);
}
}
Loading…
Cancel
Save