Skip to content
Snippets Groups Projects
Commit c47326b7 authored by Goik Martin's avatar Goik Martin
Browse files

Human vs. computer

parent 31d6d691
No related branches found
No related tags found
No related merge requests found
Showing
with 728 additions and 0 deletions
/target/
/.settings/
A1.log
.classpath
.project
<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>4.0</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>
package de.hdm_stuttgart.mi.sd1.tictactoe;
public class Board {
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;
}
}
public Board(final Player activePlayer, Player[] initial) { // For unit testing purpose.
currentActivePlayer = activePlayer;
for (int i = 0; i < numberOfFields; i++) { // Initialize board with null values.
board[i] = initial[i];
if (null != board[i]) {
numberOfMoves++;
}
}
}
public void nextPerfectMove() {
final int[] freeFields = getFreeFields();
if (0 == freeFields.length) {
return;
}
int drawOrLooseIndex = freeFields[0]; // Choose a candidate. If not confirmed, we're bound to loose anyway
for (int i = 0; i < freeFields.length; i++) {
final int freeFieldIndex = freeFields[i];
board[freeFieldIndex] = currentActivePlayer; // Set empty field to player.
final int score = getScore(
board, currentActivePlayer.getOtherPlayer(),
Helper.excludeIndex(freeFields, i));
if (currentActivePlayer.score == score) {
currentActivePlayer = currentActivePlayer.getOtherPlayer();
numberOfMoves++;
return; // Leave current field occupied by active player.
} else {
if (0 == score) {
drawOrLooseIndex = freeFieldIndex; // Better off than loosing.
}
}
board[freeFieldIndex] = null; // Reset field to empty state, continue searching.
}
board[drawOrLooseIndex] = currentActivePlayer;
numberOfMoves++;
currentActivePlayer = currentActivePlayer.getOtherPlayer();
}
public int[] getFreeFields() {
final int[] freeFields = new int[numberOfFields - numberOfMoves];
int index = 0;
for (int i = 0; i < numberOfFields; i++) {
if (null == board[i]) {
freeFields[index++] = i;
}
}
return freeFields;
}
public int getScore() { // For unit testing purpose.
final int[] freeFields = getFreeFields();
return getScore(board, currentActivePlayer, freeFields);
}
private int getScore(final Player[] evalBoard, final Player activePlayer, final int[] freeFields) {
{
final Player winner = getWinner(evalBoard);
if (null != winner) {
return winner.score; // "Winning" end state.
} else if (0 == freeFields.length) {
return 0; // "Draw" end state: Board completely populated.
}
}
boolean drawChildExists = false;
for(int i = 0; i < freeFields.length; i++) {
final int freeFieldIndex = freeFields[i];
evalBoard[freeFieldIndex] = activePlayer; // Set empty field to player.
final int score = getScore(
evalBoard, activePlayer.getOtherPlayer(),
Helper.excludeIndex(freeFields, i));
evalBoard[freeFieldIndex] = null; // Reset field to empty state.
if (activePlayer.score == score) { // Maximum or minimum (with respect to active player) has been reached.
return activePlayer.score;
} else if (0 == score) {
drawChildExists = true; // Continue searching for better (maximal or minimal) score.
}
}
if (drawChildExists) {
return 0; // There is at least one "draw" child to choose from.
} else {
return activePlayer.getOtherPlayer().score; // All board children score to the active player's enemy
}
}
public Player evaluateWinner(){
return getWinner(board);
}
/**
* Test whether one of our two players has won the game.
*
* @return The winner instance if a winner exists, null otherwise
*/
static public Player getWinner(Player[] evalBoard) {
if (null != evalBoard[4] && ( // Check both diagonals
(evalBoard[4] == evalBoard[0] && evalBoard[4] == evalBoard[8]) || //
(evalBoard[4] == evalBoard[6] && evalBoard[4] == evalBoard[2]))) { // x x
return evalBoard[4]; // x
} // x x
for (int i = 0; i < fieldSize; i++) { // Check all three columns
if (null != evalBoard[i] && // +-=
evalBoard[i] == evalBoard[i + fieldSize] &&
evalBoard[i] == evalBoard[i + 2* fieldSize]) { // +-=
return evalBoard[i]; // +-=
}
}
for (int i = 0; i < 2 * fieldSize + 1; i += fieldSize) { // Check all three rows
if (null != evalBoard[i] && // +++
evalBoard[i] == evalBoard[i + 1] && evalBoard[i] == evalBoard[i + 2]) { // ---
return evalBoard[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;
}
public void print(boolean simulateClear, boolean printNumberingHints) {
print(board, simulateClear, printNumberingHints);
}
/**
* Publish the current state to standard output using
* ASCII graphics.
*/
static private void print(final Player[] boardState, boolean simulateClear, boolean printNumberingHints) {
for (int clear = 0; simulateClear && clear < clearTerminalLineCount; clear++) {
System.out.println("\n");
}
System.out.println("\n\nPlayer " + Player.YOU.nickname + "("
+ Player.YOU.representation + ")" + "\nvs. " + Player.COM.nickname
+ "(" + Player.COM.representation + ")" + " Free fields\n");
final String tableSeparator = " ";
for (int x = 0; x < fieldSize; x++) {
// Print current values
for (int y = 0; y < fieldSize; y++) {
final int fieldIndex = y + fieldSize * x;
System.out.print(null == boardState[fieldIndex] ? " " : boardState[fieldIndex].representation);
if (y < fieldSize - 1) {
System.out.print('|' );
}
}
if (printNumberingHints) {
System.out.print(tableSeparator);
// print free field numbering hints.
for (int y = 0; y < fieldSize; y++) {
final int fieldIndex = y + fieldSize * x;
System.out.print(null == boardState[fieldIndex] ? fieldIndex + 1 : // Nerd to user perspective.
" ");
if (y < fieldSize - 1) {
System.out.print('|');
}
}
}
if (x < fieldSize - 1) {
System.out.print("\n-+-+-" + (printNumberingHints ? tableSeparator + "-+-+-" : "") );
System.out.println();
}
}
}
public Player[] getBoard() {
return board.clone();
}
}
package de.hdm_stuttgart.mi.sd1.tictactoe;
public class Helper {
static public int[] excludeIndex(final int[] values, int excludeIndex) {
final int[] ret = new int[values.length - 1];
for (int i = 0; i < excludeIndex; i++) {
ret[i] = values[i];
}
for (int i = excludeIndex; i < values.length - 1; i++) {
ret[i] = values[i + 1];
}
return ret;
}
}
package de.hdm_stuttgart.mi.sd1.tictactoe;
public enum Player {
YOU("You", 'O', 1), COM("Me (computer)", 'X', -1);
public final String nickname;
public final char representation;
public int score;
private Player other;
static { // For further explanation see
YOU.other = COM; // http://stackoverflow.com/questions/5678309/illegal-forward-reference-and-enums#5678375
COM.other = YOU;
}
public Player getOtherPlayer() {
return other;
}
Player(final String nickname, final char representation, final int score) {
this.nickname = nickname;
this.representation = representation;
this.score = score;
}
@Override
public String toString() {
return nickname + ", " + score + ", "+ representation;
}
}
package de.hdm_stuttgart.mi.sd1.tictactoe;
import java.util.Scanner;
/**
* 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.YOU.ordinal() + " = " + Player.YOU.nickname +
", other = " + Player.COM.nickname);
final int firstPlayer = scan.nextShort();
final Board board;
if (Player.YOU.ordinal() == firstPlayer) {
board = new Board(Player.YOU);
} else {
board = new Board(Player.COM);
board.nextPerfectMove();
}
board.print(true, true);
Player winner;
do {
do {
System.out.print("\n\nPlease enter next field's number:");
final short nextField = scan.nextShort();
final String errorMessage = board.nextMove(nextField);
if (null == errorMessage) {
break; // o.K., quit loop for next move.
} else {
System.err.println(errorMessage); // Field already occupied?
}
} while (true);
board.nextPerfectMove();
board.print(true, !board.allMovesFinished());
} while (null == (winner = board.evaluateWinner()) && !board.allMovesFinished());
if (null == winner) {
System.out.println("\n\nGame over: draw");
} else {
switch(winner) {
case YOU: System.out.println("\n\nCongratulations, you won!");break;
case COM: System.out.println("\n\nSorry, you lost!");break;
}
}
scan.close();
}
}
<?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
package de.hdm_stuttgart.mi.sd1.connectfour;
import org.junit.Assert;
import org.junit.Test;
import de.hdm_stuttgart.mi.sd1.tictactoe.Helper;
/**
* Testing Tic-tac-toe.
*/
public class HelperTest {
@Test
public void testOneField() {
final int[] input = {9},
result = Helper.excludeIndex(input, 0),
expectedResult = {};
Assert.assertArrayEquals(expectedResult, result);
}
@Test
public void testTwoFields0() {
final int[] input = {0, 8},
result = Helper.excludeIndex(input, 0),
expectedResult = {8};
Assert.assertArrayEquals(expectedResult, result);
}
@Test
public void testTwoFields1() {
final int[] input = {0, 8},
result = Helper.excludeIndex(input, 1),
expectedResult = {0};
Assert.assertArrayEquals(expectedResult, result);
}
@Test
public void testArrayFields0() {
final int[] input = {1,2,3},
result = Helper.excludeIndex(input, 0),
expectedResult = {2,3};
Assert.assertArrayEquals(expectedResult, result);
}
@Test
public void testArrayFields2() {
final int[] input = {1,2,3},
result = Helper.excludeIndex(input, 1),
expectedResult = {1,3};
Assert.assertArrayEquals(expectedResult, result);
}
@Test
public void testThreeFields3() {
final int[] input = {1,2,3},
result = Helper.excludeIndex(input, 2),
expectedResult = {1,2};
Assert.assertArrayEquals(expectedResult, result);
}
}
\ No newline at end of file
package de.hdm_stuttgart.mi.sd1.connectfour;
import static de.hdm_stuttgart.mi.sd1.tictactoe.Player.YOU;
import static de.hdm_stuttgart.mi.sd1.tictactoe.Player.COM;
import org.junit.Assert;
import org.junit.Test;
import de.hdm_stuttgart.mi.sd1.tictactoe.Board;
import de.hdm_stuttgart.mi.sd1.tictactoe.Player;
/**
* Testing Tic-tac-toe.
*/
public class PerfectMoveTest {
@Test
public void testWinningMove() {
final Board board = new Board(
COM, // active player.
new Player[] {
null, COM, YOU, // |X|O
// -+-+-
null, COM, YOU, // |X|O
// -+-+-
null, null, null}); // | |
board.nextPerfectMove();
final Player[] expected = new Player[] {
null, COM, YOU, // |X|O
// -+-+-
null, COM, YOU, // |X|O
// -+-+-
null, COM, null}; // |X|
Assert.assertArrayEquals(expected, board.getBoard());
}
@Test
public void testStartPlayer() {
final Board board = new Board(YOU);
board.nextMove(1);
board.nextPerfectMove();
board.print(false, true);
board.nextMove(2);
board.print(false, true);
board.nextPerfectMove();
board.print(false, true);
}
}
package de.hdm_stuttgart.mi.sd1.connectfour;
import static de.hdm_stuttgart.mi.sd1.tictactoe.Player.YOU;
import static de.hdm_stuttgart.mi.sd1.tictactoe.Player.COM;
import org.junit.Assert;
import org.junit.Test;
import de.hdm_stuttgart.mi.sd1.tictactoe.Board;
import de.hdm_stuttgart.mi.sd1.tictactoe.Player;
/**
* Testing Tic-tac-toe.
*/
public class ScoreTest {
@Test
public void testEmptyBoard() {
final Board board = new Board(
COM, // active player.
new Player[] {
null, null, null, // | |
// -+-+-
null, null, null, // | |
// -+-+-
null, null, null}); // | |
Assert.assertEquals(0, board.getScore());
}
@Test
public void testDrawTwoEnd() {
final Board board = new Board(
YOU, // active player.
new Player[] {
YOU, COM, COM, // O|X|X
// -+-+-
COM, YOU, YOU, // X|O|O
// -+-+-
null, null, COM}); // | |X
Assert.assertEquals(0, board.getScore());
}
@Test
public void testDraw1() {
final Board board = new Board(
COM, // active player.
new Player[] {
null, null, null, // | |
// -+-+-
null, YOU, null, // |O|
// -+-+-
YOU, null, COM}); // O| |X
Assert.assertEquals(YOU.score, board.getScore());
}
@Test
public void testDrawEnd() {
final Board board = new Board(
COM, // active player.
new Player[] {
YOU, COM, COM, // O|X|X
// -+-+-
COM, YOU, YOU, // X|O|O
// -+-+-
YOU, null, COM}); // O| |X
Assert.assertEquals(0, board.getScore());
}
@Test
public void testWinOne1() {
final Board board = new Board(
YOU, // active player.
new Player[] {
null, null, null, // | |
// -+-+-
null, YOU, null, // |O|
// -+-+-
YOU, COM, COM}); // O|X|X
Assert.assertEquals(YOU.score, board.getScore());
}
@Test
public void testWinTwoMoves() {
final Board board = new Board(
COM, // active player.
new Player[] {
null, YOU, COM, // |O|X
// -+-+-
COM, YOU, YOU, // X|O|O
// -+-+-
null, COM, null}); // |X|
Assert.assertEquals(COM.score, board.getScore());
}
@Test
public void testLooseNextMove() {
final Board board = new Board(
COM, // active player.
new Player[] {
null, YOU, YOU, // |O|O
// -+-+-
null, COM, YOU, // |X|O
// -+-+-
COM, null, null}); // X| |
Assert.assertEquals(YOU.score, board.getScore());
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment