From 31d6d691d14c0aa3ad2dcd7d10f3bb23afe45799 Mon Sep 17 00:00:00 2001 From: Martin Goik <goik@hdm-stuttgart.de> Date: Wed, 16 Sep 2015 19:47:18 +0200 Subject: [PATCH] Tic-Tac-Toe re-implementation along with unit tests --- .../hdm_stuttgart/mi/sd1/tictactoe/Board.java | 2 +- .../mi/sd1/connectfour/AppTest.java | 60 +++++- P/Sd1/TicTacToe/V2/.gitignore | 5 + P/Sd1/TicTacToe/V2/pom.xml | 66 +++++++ .../hdm_stuttgart/mi/sd1/tictactoe/Board.java | 185 ++++++++++++++++++ .../mi/sd1/tictactoe/TicTacToe.java | 60 ++++++ .../V2/src/main/resources/log4j2.xml | 21 ++ .../mi/sd1/connectfour/AppTest.java | 62 ++++++ P/pom.xml | 4 + 9 files changed, 456 insertions(+), 9 deletions(-) create mode 100644 P/Sd1/TicTacToe/V2/.gitignore create mode 100644 P/Sd1/TicTacToe/V2/pom.xml create mode 100644 P/Sd1/TicTacToe/V2/src/main/java/de/hdm_stuttgart/mi/sd1/tictactoe/Board.java create mode 100644 P/Sd1/TicTacToe/V2/src/main/java/de/hdm_stuttgart/mi/sd1/tictactoe/TicTacToe.java create mode 100644 P/Sd1/TicTacToe/V2/src/main/resources/log4j2.xml create mode 100644 P/Sd1/TicTacToe/V2/src/test/java/de/hdm_stuttgart/mi/sd1/connectfour/AppTest.java diff --git a/P/Sd1/TicTacToe/V1/src/main/java/de/hdm_stuttgart/mi/sd1/tictactoe/Board.java b/P/Sd1/TicTacToe/V1/src/main/java/de/hdm_stuttgart/mi/sd1/tictactoe/Board.java index 3c1bf40ec..391f96d9a 100644 --- a/P/Sd1/TicTacToe/V1/src/main/java/de/hdm_stuttgart/mi/sd1/tictactoe/Board.java +++ b/P/Sd1/TicTacToe/V1/src/main/java/de/hdm_stuttgart/mi/sd1/tictactoe/Board.java @@ -87,7 +87,7 @@ public class Board { * @return An error message indication either an occupied field or an index violation. * null if everything is o.K. */ - public String nextMove(final short field) { + public String nextMove(int field) { if (field < 0 || width * height <= field) { return "Field index out of range (0 ..." + diff --git a/P/Sd1/TicTacToe/V1/src/test/java/de/hdm_stuttgart/mi/sd1/connectfour/AppTest.java b/P/Sd1/TicTacToe/V1/src/test/java/de/hdm_stuttgart/mi/sd1/connectfour/AppTest.java index 97db873d9..56388adb9 100644 --- a/P/Sd1/TicTacToe/V1/src/test/java/de/hdm_stuttgart/mi/sd1/connectfour/AppTest.java +++ b/P/Sd1/TicTacToe/V1/src/test/java/de/hdm_stuttgart/mi/sd1/connectfour/AppTest.java @@ -3,15 +3,59 @@ package de.hdm_stuttgart.mi.sd1.connectfour; import org.junit.Assert; import org.junit.Test; +import de.hdm_stuttgart.mi.sd1.tictactoe.Board; + /** - * Unit test for simple App. + * Testing draw and win situations. */ public class AppTest { - /** - * Dummy test method - */ - @Test - public void testApp() { - Assert.assertTrue( true ); - } + @Test + public void testDraw() { + // Creating O|X|O + // -+-+- + // X|O|O + // -+-+- + // X|O|X + + final Board board = new Board(); + + board.nextMove(0); + board.nextMove(1); + board.nextMove(2); + board.nextMove(3); + board.nextMove(4); + board.nextMove(6); + board.nextMove(5); + board.nextMove(8); + + Assert.assertEquals(false, board.allMovesFinished()); + Assert.assertNull(board.evaluateWinner()); + + board.nextMove(7); + Assert.assertEquals(true, board.allMovesFinished()); + Assert.assertNull(board.evaluateWinner()); + } + + @Test + public void testWinner() { + // Creating O| |x + // -+-+- + // |O| + // -+-+- + // x| |O + + final Board board = new Board(); + + board.nextMove(0);//June + board.nextMove(2); + board.nextMove(4);//June + board.nextMove(5); + + Assert.assertEquals(false, board.allMovesFinished()); + Assert.assertNull(board.evaluateWinner()); + + board.nextMove(8);//June + Assert.assertEquals(false, board.allMovesFinished()); + Assert.assertEquals(Board.Player.PLAYER1, board.evaluateWinner()); + } } diff --git a/P/Sd1/TicTacToe/V2/.gitignore b/P/Sd1/TicTacToe/V2/.gitignore new file mode 100644 index 000000000..4ef0c0524 --- /dev/null +++ b/P/Sd1/TicTacToe/V2/.gitignore @@ -0,0 +1,5 @@ +/target/ +/.settings/ +A1.log +.classpath +.project diff --git a/P/Sd1/TicTacToe/V2/pom.xml b/P/Sd1/TicTacToe/V2/pom.xml new file mode 100644 index 000000000..399323b43 --- /dev/null +++ b/P/Sd1/TicTacToe/V2/pom.xml @@ -0,0 +1,66 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>de.hdm-stuttgart.mi</groupId> + <artifactId>lecturenotes-pom</artifactId> + <version>1.0</version> + <relativePath>../../../pom.xml</relativePath> + </parent> + + + <groupId>de.hdm-stuttgart.mi.sd1</groupId> + <artifactId>tictactoe</artifactId> + <version>1.1</version> + <packaging>jar</packaging> + + <name>TicTacToe</name> + + <url>http://www.mi.hdm-stuttgart.de/freedocs</url> + + <build> + <plugins> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.1</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>2.10.1</version> + <configuration/> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <configuration> + <transformers> + <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <manifestEntries> + <Main-Class>de.hdm_stuttgart.mi.sd1.connectfour.App</Main-Class> + </manifestEntries> + </transformer> + </transformers> + </configuration> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + </execution> + </executions> + </plugin> + + </plugins> + </build> +</project> diff --git a/P/Sd1/TicTacToe/V2/src/main/java/de/hdm_stuttgart/mi/sd1/tictactoe/Board.java b/P/Sd1/TicTacToe/V2/src/main/java/de/hdm_stuttgart/mi/sd1/tictactoe/Board.java new file mode 100644 index 000000000..c44247f3d --- /dev/null +++ b/P/Sd1/TicTacToe/V2/src/main/java/de/hdm_stuttgart/mi/sd1/tictactoe/Board.java @@ -0,0 +1,185 @@ +package de.hdm_stuttgart.mi.sd1.tictactoe; + +public class Board { + + public enum Player { + + JUNE("June", 'O'), Bill("Bill", 'X'); + + public final String nickname; + public final char representation; + + Player(final String nickname, final char representation) { + this.nickname = nickname; + this.representation = representation; + } + + public Player getOtherPlayer() { + switch (this) { + case JUNE: + return Bill; + case Bill: + return JUNE; + default: + return null; + } + } + + @Override + public String toString() { + return "" + representation; + } + } + + static private int clearTerminalLineCount = 20; // Scroll down this many lines simulating terminal clear. + static private int fieldSize = 3; + static private int numberOfFields = fieldSize * fieldSize; // Each Tic-Tac-Toe board has got 3 x 3 = 9 fields + private short numberOfMoves = 0; // Game will be over after a maximum of 9 moves + + private Player currentActivePlayer; // Player about to start next move + + final Player[] board = new Player[numberOfFields]; // Allocating a one- dimensional array, yet empty. + + public Board(final Player startingPlayer) { + currentActivePlayer = startingPlayer; + for (int i = 0; i < numberOfFields; i++) { // Initialize board with null values. + board[i] = null; + } + } + + /** + * Test whether one of our two players has won the game. + * + * @return The winner instance if a winner exists, null otherwise + */ + public Player evaluateWinner() { + + if (null != board[4] && ( // Check both diagonals + (board[4] == board[0] && board[4] == board[8]) || // + (board[4] == board[6] && board[4] == board[2]))) { // x x + return board[4]; // x + } // x x + + for (int i = 0; i < fieldSize; i++) { // Check all three columns + if (null != board[i] && // +-= + board[i] == board[i + fieldSize] && + board[i] == board[i + 2* fieldSize]) { // +-= + return board[i]; // +-= + } + } + + for (int i = 0; i < 2 * fieldSize + 1; i += fieldSize) { // Check all three rows + if (null != board[i] && // +++ + board[i] == board[i + 1] && board[i] == board[i + 2]) { // --- + return board[i]; // === + } + } + return null; + } + + /** + * Occupy the next field by player {@link #getCurrentActivePlayer()}. + * + * Fields are being numbered by: + * + * 1|2|3 + * -+-+- + * 4|5|6 + * -+-+- + * 7|8|9 + * + * @param field The field in question + * + * @return An error message indication either an occupied field or an index violation. + * null if everything is o.K. + */ + public String nextMove(final int field) { + + if (field < 1 || numberOfFields < field) { + return "Field index out of range [0 ..." + + numberOfFields + "]"; + } else { + final int fieldIndex = field - 1; // User to (computer) nerd point of view + final Player current = board[fieldIndex]; + if (current == null) { + board[fieldIndex] = currentActivePlayer; + currentActivePlayer = currentActivePlayer.getOtherPlayer(); + numberOfMoves++; + return null; + } else { + return "Field already occupied by " + current.nickname; + } + } + } + + static public void printNumberingHint() { + System.out.println("Numbering scheme:\n"); + for (int y = 0; y < 3; y++) { + for (int x = 0; x < 3; x++) { + System.out.print((char)('1' + x + y * fieldSize)); + if (x < fieldSize - 1) { + System.out.print('|'); + } + } + if (y < fieldSize - 1) { + System.out.println("\n-+-+-"); + } + } + } + + /** + * Get the player instance which will do the next move. See {@link #nextMove(short)} + * + * @return the currently active player. + */ + public Player getCurrentActivePlayer() { + return currentActivePlayer; + } + + /** + * After nine moves all fields will have been occupied. + * @return true if all possible moves have been completed, false otherwise. + */ + public boolean allMovesFinished() { + return numberOfFields <= numberOfMoves; + } + + /** + * Publish the current state to standard output using + * ASCII graphics. + */ + public void print() { + for (int clear = 0; clear < clearTerminalLineCount; clear++) { + System.out.println("\n"); + } + + System.out.println("\n\nPlayer " + Player.JUNE.nickname + "(" + + Player.JUNE.representation + ")" + "\nvs. " + Player.Bill.nickname + + "(" + Player.Bill.representation + ")" + " Free fields\n"); + + final String tableSeparator = " "; + for (int x = 0; x < fieldSize; x++) { + for (int y = 0; y < fieldSize; y++) { + final int fieldIndex = y + fieldSize * x; + System.out.print(null == board[fieldIndex] ? " " : board[fieldIndex]); + if (y < fieldSize - 1) { + System.out.print('|' ); + } + } + System.out.print(tableSeparator); + + for (int y = 0; y < fieldSize; y++) { + final int fieldIndex = y + fieldSize * x; + System.out.print(null == board[fieldIndex] ? fieldIndex + 1 : // Nerd to user perspective. + " "); + if (y < fieldSize - 1) { + System.out.print('|'); + } + } + + if (x < fieldSize - 1) { + System.out.println("\n-+-+-" + tableSeparator + "-+-+-"); + } + } + } +} diff --git a/P/Sd1/TicTacToe/V2/src/main/java/de/hdm_stuttgart/mi/sd1/tictactoe/TicTacToe.java b/P/Sd1/TicTacToe/V2/src/main/java/de/hdm_stuttgart/mi/sd1/tictactoe/TicTacToe.java new file mode 100644 index 000000000..a566b5405 --- /dev/null +++ b/P/Sd1/TicTacToe/V2/src/main/java/de/hdm_stuttgart/mi/sd1/tictactoe/TicTacToe.java @@ -0,0 +1,60 @@ +package de.hdm_stuttgart.mi.sd1.tictactoe; + +import java.util.Scanner; + +import de.hdm_stuttgart.mi.sd1.tictactoe.Board.Player; + +/** + * Playing Tic-tac-toe with two players. + * + */ +public class TicTacToe { + + /** + * @param args + * Unused + */ + public static void main(String[] args) { + + final Scanner scan = new Scanner(System.in); + + Board.printNumberingHint(); + System.out.println("\n\n"); + + System.out.println("Who is going to start? " + + Player.JUNE.ordinal() + " = " + Board.Player.JUNE.nickname + + ", other = " + Board.Player.Bill.nickname); + + final int firstPlayer = scan.nextShort(); + + final Board board; + if (Player.JUNE.ordinal() == firstPlayer) { + board = new Board(Player.JUNE); + } else { + board = new Board(Player.Bill); + } + + Board.Player winner; + do { + do { + System.out.print("\n\n" + board.getCurrentActivePlayer().nickname + ", please enter next field's number:"); + final short nextField = scan.nextShort(); + final String errorMessage = board.nextMove(nextField); + if (null == errorMessage) { + break; // We made it, quit loop. + } else { + System.err.println(errorMessage); + } + } while (true); + board.print(); + } while (null == (winner = board.evaluateWinner()) && !board.allMovesFinished()); + + if (null == winner) { + System.out.println("\n\nGame over: draw"); + } else { + System.out.println("\n\nCongratulations, " + winner.nickname + "!"); + } + + scan.close(); + } +} diff --git a/P/Sd1/TicTacToe/V2/src/main/resources/log4j2.xml b/P/Sd1/TicTacToe/V2/src/main/resources/log4j2.xml new file mode 100644 index 000000000..52f0a47cb --- /dev/null +++ b/P/Sd1/TicTacToe/V2/src/main/resources/log4j2.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <File name="A1" fileName="A1.log" append="false"> + <PatternLayout pattern="%t %-5p %c{2} - %m%n"/> + </File> + <Console name="STDOUT" target="SYSTEM_OUT"> + <PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/> + </Console> + </Appenders> + <Loggers> + + <!-- You my want to define class or package level per-logger rules --> + <Logger name="de.hdm_stuttgart.mi.sd1.connectfour.App" level="debug"> + <AppenderRef ref="A1"/> + </Logger> + <Root level="info"> + <AppenderRef ref="STDOUT"/> + </Root> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/P/Sd1/TicTacToe/V2/src/test/java/de/hdm_stuttgart/mi/sd1/connectfour/AppTest.java b/P/Sd1/TicTacToe/V2/src/test/java/de/hdm_stuttgart/mi/sd1/connectfour/AppTest.java new file mode 100644 index 000000000..56647561c --- /dev/null +++ b/P/Sd1/TicTacToe/V2/src/test/java/de/hdm_stuttgart/mi/sd1/connectfour/AppTest.java @@ -0,0 +1,62 @@ +package de.hdm_stuttgart.mi.sd1.connectfour; + +import org.junit.Assert; +import org.junit.Test; + +import de.hdm_stuttgart.mi.sd1.tictactoe.Board; + +/** + * Testing Tic-tac-toe. + */ +public class AppTest { + + @Test + public void testDraw() { + // Creating O|X|O + // -+-+- + // X|O|O + // -+-+- + // X|O|X + + final Board board = new Board(Board.Player.JUNE); + + board.nextMove(1); + board.nextMove(2); + board.nextMove(3); + board.nextMove(4); + board.nextMove(5); + board.nextMove(7); + board.nextMove(6); + board.nextMove(9); + + Assert.assertEquals(false, board.allMovesFinished()); + Assert.assertNull(board.evaluateWinner()); + + board.nextMove(8); + Assert.assertEquals(true, board.allMovesFinished()); + Assert.assertNull(board.evaluateWinner()); + } + + @Test + public void testWinner() { + // Creating O| |x + // -+-+- + // |O| + // -+-+- + // x| |O + + final Board board = new Board(Board.Player.JUNE); + + board.nextMove(1);//June + board.nextMove(3); + board.nextMove(5);//June + board.nextMove(6); + + Assert.assertEquals(false, board.allMovesFinished()); + Assert.assertNull(board.evaluateWinner()); + + board.nextMove(9);//June + Assert.assertEquals(false, board.allMovesFinished()); + Assert.assertEquals(Board.Player.JUNE, board.evaluateWinner()); + } +} diff --git a/P/pom.xml b/P/pom.xml index 36b588233..269417099 100644 --- a/P/pom.xml +++ b/P/pom.xml @@ -87,6 +87,10 @@ <module>Sd1/Array/StringArray2Html</module> <module>Sd1/StringLengthSort/Solution</module> + + <module>Sd1/TicTacToe/V1</module> + <module>Sd1/TicTacToe/V2</module> + <module>Sd1/Wc/wc</module> <module>Sd1/Wc/readFile</module> -- GitLab