commit 3db4f94e6af1ff1599728dcf3f5e21a65a4801ea Author: Felix Steghofer Date: Sun Dec 4 01:25:28 2022 +0100 init diff --git a/Enigma/Enigma.class b/Enigma/Enigma.class new file mode 100644 index 0000000..07e5767 Binary files /dev/null and b/Enigma/Enigma.class differ diff --git a/Enigma/Enigma.java b/Enigma/Enigma.java new file mode 100644 index 0000000..feb9241 --- /dev/null +++ b/Enigma/Enigma.java @@ -0,0 +1,67 @@ +/** + * Enigma Class with method for en-/decoding of characters. + * Constructs the Enigma. + */ +public class Enigma { + + /** + * Patchboard of the Enigma. + */ + private PatchBoard patchboard; + + /** + * Reflector of the Engima. + */ + private Reflector reflector; + + /** + * Array of variable mount of Enigma's rotors. + */ + private Rotor[] rotor; + + /** + * Creates the Enigma with patchboard, reflector and all the rotors. + * Arguments are committed by the Shell. + * @param tickPos tick-position-array for all rotors + * @param initialPos initial-position-array + * @param permu permutation-array-array + * @param patchboardpermu Permutation for the patchboard + * @param reflectorpermu Permutation for the reflector + */ + Enigma(final int[] tickPos, final int[] initialPos, final int[][] permu + , final int[] patchboardpermu, final int[] reflectorpermu) { + this.patchboard = new PatchBoard(patchboardpermu); + this.reflector = new Reflector(reflectorpermu); + rotor = new Rotor[tickPos.length]; + for (int i = 0; i < tickPos.length; i++) { + rotor[i] = new Rotor(tickPos[i], + initialPos[i], permu[i]); + if (0 < i) { + this.rotor[i - 1].setNext(rotor[i]); + } + } + } + + /** + * Method for en-/decoding of a Character. Calls one by one the patchboard + * , all the rotors, the Reflector, back again all the rotors and finally + * again the patchboard like in the Enigma, used by the Germans in WW2. + * @param symbol int-value of a Character + * @return en-/decoded value of a Character + */ + public final int encode(final int symbol) { + int i = symbol; + i = patchboard.encode(i); + for (int j = 0; j < rotor.length; j++) { + i = rotor[j].encode(i); + } + i = reflector.encode(i); + for (int k = rotor.length - 1; k >= 0; k--) { + i = this.rotor[k].decode(i); + } + i = patchboard.decode(i); + rotor[0].tick(); + return i; + } + +} \ No newline at end of file diff --git a/Enigma/EnigmaComponent.class b/Enigma/EnigmaComponent.class new file mode 100644 index 0000000..8afe4e8 Binary files /dev/null and b/Enigma/EnigmaComponent.class differ diff --git a/Enigma/EnigmaComponent.java b/Enigma/EnigmaComponent.java new file mode 100644 index 0000000..83ff181 --- /dev/null +++ b/Enigma/EnigmaComponent.java @@ -0,0 +1,33 @@ +/** + * Interface EnigmaComponent unifies rotors, patchboard and reflector + * of an enigma. + */ +interface EnigmaComponent { + + /** + * Applies the substitution chiffre of this enigma component in the current + * state to symbol. + * + * @param symbol The symbol to encode with the substitution chiffre of this + * enigma component in its current state. + * @return The symbol which is the encoded symbol of the parameter symbol. + */ + int encode(int symbol); + + /** + * Applies the inverse substitution chiffre of this enigma component in the + * current state to letter. + * + * @param symbol The symbol to decode with the (inverse) substitution + * chiffre of this enigma component in its current state. + * @return The symbol which is the decoded symbol of the parameter symbol. + */ + int decode(int symbol); + + /** + * In case this component can do ticks, this component performs its next + * tick. In case this triggers ticks of subsequent components, their ticks + * are also triggered. + */ + void tick(); +} \ No newline at end of file diff --git a/Enigma/PatchBoard.class b/Enigma/PatchBoard.class new file mode 100644 index 0000000..e8d71e4 Binary files /dev/null and b/Enigma/PatchBoard.class differ diff --git a/Enigma/PatchBoard.java b/Enigma/PatchBoard.java new file mode 100644 index 0000000..a84cb39 --- /dev/null +++ b/Enigma/PatchBoard.java @@ -0,0 +1,59 @@ +/** + * Patchboard for a Enigma. + */ +public class PatchBoard implements EnigmaComponent { + /** + * Permutation for the patchboard. + */ + private int[] permu; + + /** + * Inverse permutation for the patchboard. + */ + private int[] invPermu; + + /** + * Constructor for the patchboard. + * @param permu permutation + */ + PatchBoard(final int[] permu) { + this.permu = permu; + this.invPermu = calcInvPermu(permu); + } + + /** + * {@inheritDoc} + */ + public final int encode(final int i) { + return permu[i]; + } + + /** + * {@inheritDoc} + */ + public final int decode(final int i) { + return invPermu[i]; + } + + /** + * {@inheritDoc} + */ + public final void tick() { + } + /** + * Calculates the inverese permutation. + * @param permu permutation + * @return inverse permutation + */ + private int[] calcInvPermu(final int[] permu) { + int[] array = new int[permu.length]; + for (int i = 0; i < permu.length; i++) { + for (int k = 0; k < permu.length; k++) { + if (i == permu[k]) { + array[i] = k; + } + } + } + return array; + } +} \ No newline at end of file diff --git a/Enigma/Reflector.class b/Enigma/Reflector.class new file mode 100644 index 0000000..d182596 Binary files /dev/null and b/Enigma/Reflector.class differ diff --git a/Enigma/Reflector.java b/Enigma/Reflector.java new file mode 100644 index 0000000..f9d5258 --- /dev/null +++ b/Enigma/Reflector.java @@ -0,0 +1,40 @@ +/** + * Reflector for the Enigma. + * + */ +public class Reflector implements EnigmaComponent { + + /** + * Permutation of the reflector. + */ + private int[] permu; + /** + * Permutation of reflector. + * @param permu Permutation + */ + Reflector(final int[] permu) { + this.permu = permu; + } + + /** + * {@inheritDoc} + */ + public final int encode(final int i) { + return permu[i]; + } + + /** + * Method is not required in the reflector.
+ * {@inheritDoc} + */ + public final int decode(final int i) { + return 0; + } + + /** + * {@inheritDoc} + */ + public void tick() { + + } +} \ No newline at end of file diff --git a/Enigma/Rotor.class b/Enigma/Rotor.class new file mode 100644 index 0000000..c114bb6 Binary files /dev/null and b/Enigma/Rotor.class differ diff --git a/Enigma/Rotor.java b/Enigma/Rotor.java new file mode 100644 index 0000000..80717da --- /dev/null +++ b/Enigma/Rotor.java @@ -0,0 +1,109 @@ +/** + * Class for the rotors of the Enigma. + * + */ +public class Rotor implements EnigmaComponent { + /** + * Position where rotor "ticks" next rotor. + */ + private int tickPos; + + /** + * Current position of the rotor. + */ + private int initialPos; + + /** + * If Rotor has succesor. + */ + private Rotor next; + + /** + * Permutation of the rotor. + */ + private int[] permu; + + /** + * Inverse permutation of the rotor. + */ + private int[] invPermu; + + /** + * @param tickPos Position where rotor "ticks" next rotor + * @param initialPos Current position of the rotor + * @param permu Permutation of the rotor + */ + Rotor(final int tickPos, final int initialPos, final int[] permu) { + this.tickPos = tickPos; + this.initialPos = initialPos; + this.permu = permu; + this.invPermu = calcInvPermu(permu); + this.next = null; + } + + /** + * Setter for the next-rotor-pointer. + * @param next Next rotor + */ + public final void setNext(final Rotor next) { + this.next = next; + } + + /** + * Calculates the inverese permutation. + * @param permu permutation + * @return inverse permutation + */ + private int[] calcInvPermu(final int[] permu) { + int[] array = new int[permu.length]; + for (int i = 0; i < permu.length; i++) { + for (int k = 0; k < permu.length; k++) { + if (i == permu[k]) { + array[i] = k; + } + } + } + return array; + } + + /** + * {@inheritDoc} + */ + public final int encode(final int i) { + if (((permu[((initialPos + i) % permu.length)] - initialPos + + permu.length) % permu.length) < 0) { + return (((permu[((initialPos + i) % permu.length)] + - initialPos + permu.length) + % permu.length) + permu.length); + } + return (permu[((initialPos + i) % permu.length)] + - initialPos + permu.length) % permu.length; + } + + /** + * {@inheritDoc} + */ + public final int decode(final int i) { + if (((invPermu[((initialPos + i) % permu.length)] + - initialPos + permu.length) % permu.length) < 0) { + return (((invPermu[((initialPos + i) % permu.length)] + - initialPos + permu.length) + % permu.length) + permu.length); + } + return (invPermu[((initialPos + i) % permu.length)] + - initialPos + permu.length) % permu.length; + } + + /** + * {@inheritDoc} + */ + public final void tick() { + if (initialPos == permu.length) { + initialPos = 0; + } + initialPos++; + if (initialPos == (tickPos + 1) && next != null) { + next.tick(); + } + } +} \ No newline at end of file diff --git a/Enigma/Shell.class b/Enigma/Shell.class new file mode 100644 index 0000000..3d8dbac Binary files /dev/null and b/Enigma/Shell.class differ diff --git a/Enigma/Shell.java b/Enigma/Shell.java new file mode 100644 index 0000000..9172821 --- /dev/null +++ b/Enigma/Shell.java @@ -0,0 +1,252 @@ +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; + +/** + * Main Class for interaction with the user. + * + */ +public final class Shell { + /** + * number of arguments / rotor. + */ + private static final int ROTORARGS = 3; + /** + * Patchboard-permutation. + */ + private static final int[] PATCHBOARDPERMU = new int[] {9, 22, 20, 11, 2, 12 + , 13, 14, 7, 15, 16, 25, 24, 23, 8, 17, 0, 3, 10, 4, 6, 21, 1, 19 + , 18, 5}; + + /** + * Reflector- permutation. + */ + private static final int[] REFLECTORPERMU = new int[] {8, 12, 4, 19, 2, 6, 5 + , 17, 0, 24, 18, 16, 1, 25, 23, 22, 11, 7, 10, 3, 21, 20, 15, 14 + , 9, 13}; + + /** + * Amount of Symbols the alphabet uses. + */ + private static int symbols = PATCHBOARDPERMU.length; + + private Shell() { } + + /** + * Convert char-Array to Enigma-permutation-array. + * @param charArray char-array to convert + * @return Enigma-compatible permutation array + */ + private static int[] convertCharInt(final char[] charArray) { + int[] intArray = new int[charArray.length]; + for (int i = 0; i < charArray.length; i++) { + intArray[i] = (charArray[i] - 65); + } + return intArray; + } + + /** + * Convert Enigma-int to char. + * @param i Enigma-int + * @return char + */ + private static char convertIntChar(final int i) { + return (char) (i + 65); + } + + /** + * Checks if every Parameter for a Enigma exists. + * @param stringArray Array of all programm parameters + * @return true if right number of parameters are given
+ * false if wrong number of parameters or parameters + * are zero + */ + private static boolean checkArgsLength(final String[] stringArray) { + return stringArray.length % ROTORARGS == 0 && stringArray.length != 0; + } + + /** + * Extracts the Permutation-Array Elements out of the Arguments + * given by the User.
Every third Element is a Permutation-Array. + * Furthermore, the lengths of the Array is checked to handle faults. + * @param stringAr the String entered by the user. + * @return Permutation-Array[][] for every Rotor as a + * char-array or
null if any error occurs. + */ + private static int[][] extractPermuArray(final String[] stringAr) { + int[][] intArray = new int[(stringAr.length / ROTORARGS)] + [Shell.symbols]; + for (int i = 0; i < (stringAr.length) / ROTORARGS; i++) { + if (stringAr[(i * ROTORARGS)].length() == Shell.symbols) { + intArray[i] = convertCharInt( + stringAr[(i * ROTORARGS)].toCharArray()); + } else { return null; } + } + return intArray; + } + + /** + * Extract the tick-Positions out of string-array into a int-array. + * @param stringAr All Arguments committed to the Programm + * @return int-array with rotors tick-positions
+ * or null if tick-positition has more then + * one character + */ + private static int[] extractTickPosArray(final String[] stringAr) { + int[] intArray = new int[(stringAr.length / ROTORARGS)]; + for (int i = 0; i < (stringAr.length / ROTORARGS); i++) { + if (stringAr[1 + (i * ROTORARGS)].toCharArray().length == 1) { + intArray[i] = stringAr[1 + (i * ROTORARGS)].charAt(0) - 65; + } else { return null; } + } + return intArray; + } + + /** + * Extract the initial-Positions out of string-array into a int-array. + * @param stringAr All Arguments committed to the Programm + * @return int-array with rotors initial-positions
+ * or null if initial-positition has more then + * one character + */ + private static int[] extractInitialPosArray(final String[] stringAr) { + int[] intArray = new int[(stringAr.length / ROTORARGS)]; + for (int i = 0; i < (stringAr.length / ROTORARGS); i++) { + if (stringAr[2 + (i * ROTORARGS)].toCharArray().length == 1) { + intArray[i] = stringAr[2 + (i * ROTORARGS)].charAt(0) - 65; + } else { return null; } + } + return intArray; + } + /** + * Checks if char is a capital-Letter. + * @param ch Character + * @return true
/ + * or false + */ + private static boolean isEnigmaChar(final int ch) { + if (ch < 0 || ch >= Shell.symbols) { + return false; + } + return true; + } + + /** + * Checks if char-array is a capital-Letter-array. + * @param ch Character-array + * @return true
/ + * or false + */ + private static boolean isEnigmaCharArray(final int[] ch) { + for (int i = 0; i < ch.length; i++) { + if (!isEnigmaChar(ch[i])) { + return false; + } + } + return true; + } + + /** + * Checks if char-array-array is a capital-Letter-array-array. + * @param ch Character-array-array + * @return true
/ + * or false + */ + private static boolean isEnigmaCharArray(final int[][] ch) { + for (int i = 0; i < ch.length; i++) { + if (!isEnigmaCharArray(ch[i])) { + return false; + } + } + return true; + } + /** + * Checks if a permutation contains a char twice. + * @param permu Permutation + * @return true if duplicate char is found
+ * false if chars are unique + */ + private static boolean hasDuplicateChar(final int[][] permu) { + boolean[][] foo = new boolean[permu.length][permu[0].length]; + int i; + int j; + for (i = 0; i < permu.length; i++) { + for (j = 0; j < permu[i].length; j++) { + if (foo[i][permu[i][j]]) { + return true; + } else { + foo[i][permu[i][j]] = true; + } + } + } + return false; + } + + /** + * Main method for the Enigma-Simulation. + * @param args All parameter given to the Shell + * @throws IOException IOException + */ + public static void main(final String[] args) throws IOException { + int[][] permu = new int[(args.length / ROTORARGS)][Shell.symbols]; + int[] tickPos = new int[(args.length / ROTORARGS)]; + int[] initialPos = new int[(args.length / ROTORARGS)]; + Enigma myEnigma; + + if (checkArgsLength(args)) { + if (extractPermuArray(args) != null) { + permu = extractPermuArray(args); + } else { + System.out.println("Error! Wrong Permutation!"); + return; + } + if (extractTickPosArray(args) != null) { + tickPos = extractTickPosArray(args); + } else { + System.out.println("Error! wrong Tick Position!"); + return; + } + if (extractInitialPosArray(args) != null) { + initialPos = extractInitialPosArray(args); + } else { + System.out.println("Error! wrong initial Position!"); + return; + } + } else { + System.out.println("Error: wrong number of Arguments"); + return; + } + //Check if only Capital Letters are used + if (!isEnigmaCharArray(permu) || !isEnigmaCharArray(tickPos) + || !isEnigmaCharArray(initialPos)) { + System.out.print("Error! wrong character in config"); + return; + } + if (hasDuplicateChar(extractPermuArray(args))) { + System.out.println("Error! Duplicate char in Permutation"); + } + + BufferedReader br = + new BufferedReader(new InputStreamReader(System.in)); + + myEnigma = new Enigma(tickPos, initialPos, permu, PATCHBOARDPERMU + , REFLECTORPERMU); + + + while (true) { + String stdinp = br.readLine(); + if (stdinp.equals("")) { + return; + } + int[] ciphertxt = convertCharInt(stdinp.toCharArray()); + if (!isEnigmaCharArray(ciphertxt)) { + System.out.print("Error! Wrong input"); + return; + } + for (int i = 0; i < ciphertxt.length; i++) { + System.out.print(convertIntChar(myEnigma.encode(ciphertxt[i]))); + } + System.out.println(); + } + } +} \ No newline at end of file diff --git a/Reversi/Board.java b/Reversi/Board.java new file mode 100644 index 0000000..ada7158 --- /dev/null +++ b/Reversi/Board.java @@ -0,0 +1,121 @@ +package reversi.model; + + +/** + * Interface for a Reversi game, also known as Othello. + * + * A human plays against the machine. + */ +public interface Board extends Cloneable { + + /** + * The number of rows and columns of the game grid. Originally 8. + * Here, even and at least 4. + */ + int SIZE = 8; + + /** + * Get the player who should start or already has started the game. + * + * @return The player who makes the initial move. + */ + Player getFirstPlayer(); + + /** + * Get the player who owns the next game turn. + * + * @return The player who is allowed to make the next turn. + */ + Player next(); + + /** + * Execute a human move. + * + * @param row The slot's row number where a tile of the human player should + * be placed on. + * @param col The slot's column number where a tile of the human player + * should be placed on. + * @return {@code true} if and only if move was successful, e.g., the + * defined slot was not occupied and at least one tile of the + * machine was reversed. + * @throws IllegalMoveException If the game is already over, or it is not + * the human's turn. + * @throws IllegalArgumentException If the provided parameters are + * invalid, e.g., the defined slot was is not on the grid. + */ + boolean move(int row, int col); + + /** + * Execute a machine move. + * + * @throws IllegalMoveException If the game is already over, or it is not + * the machine's turn. + */ + void machineMove(); + + /** + * Set the skill level of the machine. + * + * @param level The skill as a number, must be at least 1. + */ + void setLevel(int level); + + /** + * Check if the game is over. Either one player has won or there is a tie, + * i.e., no player can perform a move any more. + * + * @return {@code true} if and only if the game is over. + */ + boolean gameOver(); + + /** + * Check if the game state is won. + * + * @return The winner or nobody in case of a tie. + */ + Player getWinner(); + + /** + * Get the number of human tiles currently placed on the grid. + * + * @return The number of human tiles. + */ + int getNumberOfHumanTiles(); + + /** + * Get the number of machine tiles currently placed on the grid. + * + * @return The number of machine tiles. + */ + int getNumberOfMachineTiles(); + + /** + * Get the content of the slot at the specified coordinates. Either it + * contains a tile of one of the two players already or it is empty. + * + * @param row The row of the slot in the game grid. + * @param col The column of the slot in the game grid. + * @return The slot's content. + */ + Player getSlot(int row, int col); + + /** + * Deep-copy the board. + * + * @return A clone. + */ + Board clone(); + + /** + * Get the string representation of this board as row x column matrix. Each + * slot is represented by one the three chars '.', 'X', or 'O'. '.' means + * that the slot currently contains no tile. 'X' means that it contains a + * tile of the human player. 'O' means that it contains a machine tile. In + * contrast to the rows, the columns are whitespace separated. + * + * @return The string representation of the current Reversi game. + */ + @Override + String toString(); + +} \ No newline at end of file diff --git a/Reversi/Field.java b/Reversi/Field.java new file mode 100644 index 0000000..fdb6ba6 --- /dev/null +++ b/Reversi/Field.java @@ -0,0 +1,42 @@ +package reversi.model; + +/** + * Class for a reversi field. + */ +public class Field { + + /** + * Row of the field. + */ + private int row; + + /** + * Column of the field. + */ + private int col; + + /** + * Constructs a new field. + * + * @param row row of the field + * @param col column of the field + */ + public Field(final int row, final int col) { + this.row = row; + this.col = col; + } + /** + * + * @return The row of the field. + */ + public final int row() { + return row; + } + /** + * + * @return the column of the field. + */ + public final int col() { + return col; + } +} \ No newline at end of file diff --git a/Reversi/GameNode.java b/Reversi/GameNode.java new file mode 100644 index 0000000..89d0790 --- /dev/null +++ b/Reversi/GameNode.java @@ -0,0 +1,164 @@ +package reversi.model; + +import java.util.LinkedList; + +/** + * Class for a gamenode. Is needed to build a gametree for e.g. minmax + * algorithm. A node represents a virtual move. + * + */ +public class GameNode { + + /** + * Reversi-board of the node. + */ + private Reversi state; + + /** + * Scoring of the node. + */ + private double score; + + /** + * The Move. + */ + private Field move; + + /** + * Children of the node. + */ + private LinkedList children; + + /** + * Constructor for the root node. + * + * @param move Root move + * @param state Board of the root node + */ + GameNode(final Field move, final Reversi state) { + this.move = move; + this.state = state; + this.children = new LinkedList(); + } + + /** + * Constructor for node. + * + * @param move Move of node + * @param state Board of node + * @param parent Parent of node + */ + GameNode(final Field move, final Reversi state, final GameNode parent) { + this(move, state); + parent.children.add(this); + } + + /** + * + * @param score Score to set + */ + public final void setScore(final double score) { + this.score = score; + } + + /** + * + * @return Score + */ + public final double getScore() { + return score; + } + + /** + * + * @return Board + */ + public final Reversi getState() { + return state; + } + + /** + * + * @return Move + */ + public final Field getMove() { + return move; + } + + /** + * + * @return Children + */ + public final LinkedList getChildren() { + return children; + } + + /** + * Is needed to calculate the combined score of the parent node. + * + * @return GameNode with highest score + */ + public final GameNode getChildrensHighestScore() { + GameNode highest = children.getFirst(); + + for (GameNode node : children) { + if (node.getScore() > highest.getScore()) { + highest = node; + } + } + return highest; + } + + /** + * Is needed to calculate the combined score of the parent node. + * + * @return GameNode with lowest score + */ + public final GameNode getChildrensLowestScore() { + GameNode lowest = children.getFirst(); + + for (GameNode node : children) { + if (node.getScore() < lowest.getScore()) { + lowest = node; + } + } + return lowest; + } + + /** + * + * @return If node has at least one child + */ + public final boolean hasChild() { + return (children.size() > 0); + } + + /** + * Set children of a node. + */ + public final void fillNode() { + for (Field move : generateChildren()) { + Reversi tmpBoard = this.getState().clone(); + tmpBoard.performMove(move.row(), move.col()); + new GameNode(move, tmpBoard, this); + } + } + + /** + * + * @return Linkedlist with possible moves in the + * current reversi-board. + */ + private LinkedList generateChildren() { + LinkedList movesList = new LinkedList(); + + for (int row = 0; row < Board.SIZE; row++) { + for (int col = 0; col < Board.SIZE; col++) { + if (getState().isValidMove(row, col)) { + movesList.add(new Field(row, col)); + } + } + } + return movesList; + } +} \ No newline at end of file diff --git a/Reversi/GameTree.java b/Reversi/GameTree.java new file mode 100644 index 0000000..c98c5cb --- /dev/null +++ b/Reversi/GameTree.java @@ -0,0 +1,75 @@ +package reversi.model; + +/** + * Class for a GameTree. + */ +public class GameTree { + + /** + * Root gamenode of the gametree. + */ + private GameNode root; + + /** + * Constructs a new gametree. + * + * @param board of the root node + * @param level depth of the tree + */ + GameTree(final Reversi board, final int level) { + this.root = new GameNode(new Field(0, 0), board.clone()); + fillTree(root, level); + generateScores(root); + } + + /** + * + * @return Root of the gametree. + */ + public final GameNode getRoot() { + return root; + } + + /** + * Fill the Tree with nodes. + * + * @param node Node to fill (in most cases root gamenode) + * @param depth depth of the tree (level) + */ + private void fillTree(final GameNode node, final int depth) { + if (depth > 0) { + node.fillNode(); + + for (GameNode n : node.getChildren()) { + fillTree(n, (depth - 1)); + } + } + } + + /** + * Generates and sets the score of a Gamenode. + * + * @param node Gamenode to set + */ + public final void generateScores(final GameNode node) { + Reversi currentBoard = node.getState(); + + if (!node.hasChild()) { + node.setScore(currentBoard.score()); + } else { + double score; + score = currentBoard.score(); + + for (GameNode child : node.getChildren()) { + generateScores(child); + } + + if (!currentBoard.next().getType()) { + score += node.getChildrensHighestScore().getScore(); + } else { + score += node.getChildrensLowestScore().getScore(); + } + node.setScore(score); + } + } +} \ No newline at end of file diff --git a/Reversi/IllegalMoveException.java b/Reversi/IllegalMoveException.java new file mode 100644 index 0000000..f3d3af0 --- /dev/null +++ b/Reversi/IllegalMoveException.java @@ -0,0 +1,31 @@ +package reversi.model; + +import java.awt.Toolkit; + +import javax.swing.JFrame; +import javax.swing.JOptionPane; + +/** + * Class for a Illegal move in reversi game. + */ +public class IllegalMoveException extends RuntimeException { + + private static final long serialVersionUID = 240330120795325167L; + + /** + * Prints an error or plays a "beep" if a illegal move is made. + * A beep is more common but not so easy to implement for various + * OS, so the sound is only played on Windows XP or 7. + * + * @param s Error to print + */ + public IllegalMoveException(final String s) { + if (System.getProperty("os.name").equals("Windows 7") + || System.getProperty("os.name").equals("Windows XP")) { + Toolkit.getDefaultToolkit().beep(); + } else { + JFrame screen = new JFrame(); + JOptionPane.showMessageDialog(screen, s); + } + } +} \ No newline at end of file diff --git a/Reversi/MachineMoveThread.java b/Reversi/MachineMoveThread.java new file mode 100644 index 0000000..e3f3c45 --- /dev/null +++ b/Reversi/MachineMoveThread.java @@ -0,0 +1,42 @@ +package reversi.controller; + +import reversi.model.Player; +import reversi.model.Reversi; +import reversi.view.ReversiView; + +/** + * Creates a new thread for the machine move. + */ +public class MachineMoveThread extends Thread { + + private Reversi model; + private ReversiView view; + private Player bot; + + MachineMoveThread(final Reversi model, final ReversiView view) { + this.model = model; + this.view = view; + this.bot = model.next(); + } + @Override + public final void run() { + this.model.machineMove(); + javax.swing.SwingUtilities.invokeLater(new Runnable() { + @Override + public final void run() { + view.updatePlayField(model); + view.setMovePossible(true); + } + }); + + if (model.next() == bot && !model.gameOver()) { + this.run(); + javax.swing.SwingUtilities.invokeLater(new Runnable() { + @Override + public final void run() { + view.viewMessage("Human has to miss a turn"); + } + }); + } + } +} \ No newline at end of file diff --git a/Reversi/Player.java b/Reversi/Player.java new file mode 100644 index 0000000..1cc96a2 --- /dev/null +++ b/Reversi/Player.java @@ -0,0 +1,27 @@ +package reversi.model; +/** + * Class for a player of reversi. + */ +public class Player { + /** + * Indicates if a player is human. + */ + private Boolean isHuman; + + /** + * Constructs a new player. + * + * @param isHuman is the player human? + */ + public Player(final boolean isHuman) { + this.isHuman = isHuman; + } + + /** + * + * @return Is player human? + */ + public final boolean getType() { + return isHuman; + } +} \ No newline at end of file diff --git a/Reversi/Reversi.java b/Reversi/Reversi.java new file mode 100644 index 0000000..e1f7e59 --- /dev/null +++ b/Reversi/Reversi.java @@ -0,0 +1,527 @@ +package reversi.model; + +/** + * Class for reversi game. A human player tries to beat a bot with modifiable + * difficulty level. + */ +public class Reversi implements Board { + + private Player bot; + private Player human; + + /** + * Player with the next move. + */ + private Player currentPlayer; + private Player firstPlayer; + + /** + * Playboard saves the tiles. + */ + private Player[][] playBoard; + + /** + * Gametree for the bot to calculate future moves. + */ + private GameTree gameTree; + + /** + * Vectors to traverse the 8 directions around a tile. + */ + private Field[] vectors; + + /** + * Saves if game is over. + */ + private boolean gameOver; + + /** + * Standard weighting of the playboard. Used by the bot to calculate + * gametree. + */ + private double[][] weighting = new double[][] + {{9999, 5, 500, 200, 200, 500, 5, 9999}, + {5, 1, 50, 150, 150, 50, 1, 5}, + {500, 50, 250, 100, 100, 250, 50, 500}, + {200, 150, 100, 50, 50, 100, 150, 200}, + {200, 150, 100, 50, 50, 100, 150, 200}, + {500, 50, 250, 100, 100, 250, 50, 500}, + {5, 1, 50, 150, 150, 50, 1, 5}, + {9999, 5, 500, 200, 200, 500, 5, 9999}}; + + /** + * Difficulty level for the human. Represents the depth of the gametree. + */ + private int level = 3; + + /** + * Constructs a new reversi game. + * + * @param human Human player + * @param bot Machine player + * @param firstPlayer Player that starts + */ + public Reversi(Player human, Player bot, Player firstPlayer) { + this.bot = bot; + this.human = human; + this.firstPlayer = firstPlayer; + this.playBoard = setInitialPositions(); + currentPlayer = firstPlayer; + this.vectors = generateVectors(); + } + + /** + * {@inheritDoc} + */ + public final Player getFirstPlayer() { + return firstPlayer; + } + + /** + * {@inheritDoc} + */ + public final Player next() { + return currentPlayer; + } + + + /** + * {@inheritDoc} + */ + public final boolean move(final int row, final int col) { + if (currentPlayer != human) { + return false; + } + + if (!isValidMove(row, col)) { + new IllegalMoveException("Error! invalid move at (" + + (row + 1) + ", " + (col + 1) + ")."); + return false; + } + performMove(row, col); + return true; + } + + /** + * {@inheritDoc} + */ + public final void machineMove() { + if (currentPlayer != bot) { + return; + } + gameTree = new GameTree(this, level); + Field bestMove = gameTree.getRoot() + .getChildrensHighestScore().getMove(); + + performMove(bestMove.row(), bestMove.col()); + } + + /** + * {@inheritDoc} + */ + public final void setLevel(final int level) { + this.level = level; + } + + /** + * {@inheritDoc} + */ + public final boolean gameOver() { + return gameOver; + } + + /** + * {@inheritDoc} + */ + public final Player getWinner() { + if (getNumberOfHumanTiles() > getNumberOfMachineTiles()) { + return human; + } else if (getNumberOfHumanTiles() == getNumberOfMachineTiles()) { + return null; + } else { + return bot; + } + + } + + /** + * {@inheritDoc} + */ + public final int getNumberOfHumanTiles() { + return numberOfTiles(human); + } + + /** + * {@inheritDoc} + */ + public final int getNumberOfMachineTiles() { + return numberOfTiles(bot); + } + + /** + * {@inheritDoc} + */ + public final int getNumberOfTilesSet() { + int n = (getNumberOfHumanTiles() + getNumberOfMachineTiles()); + return n; + } + + /** + * {@inheritDoc} + */ + public final Player getSlot(final int row, final int col) { + return playBoard[row][col]; + } + + @Override + public final Reversi clone() { + Reversi boardCopy = new Reversi(human, bot, firstPlayer); + boardCopy.currentPlayer = currentPlayer; + Player[][] playBoardCopy = new Player[SIZE][SIZE]; + + for (int row = 0; row < SIZE; row++) { + for (int col = 0; col < SIZE; col++) { + playBoardCopy[row][col] = playBoard[row][col]; + } + } + boardCopy.playBoard = playBoardCopy; + return boardCopy; + } + + /** + * Generate the vectors for the 8 directions. array[0] = 0°, + * array[1] = 45°... + * + * @return boolean-array with all vector fields + */ + private Field[] generateVectors() { + Field[] vector = { + new Field(-1, 0), + new Field(-1, +1), + new Field(0, +1), + new Field(+1, +1), + new Field(+1, 0), + new Field(+1, -1), + new Field(0, -1), + new Field(-1, -1)}; + return vector; + } + + /** + * Sets two tiles/player at the beginning of the match in the middle of + * of the playboard. + * + * @return Player-array + */ + private Player[][] setInitialPositions() { + Player[][] playBoard = new Player[SIZE][SIZE]; + + for (int row = 0; row < SIZE; row++) { + for (int col = 0; col < SIZE; col++) { + if (((SIZE / 2) - 1 == row || SIZE / 2 == row) + && ((SIZE - col - 1) == row)) { + playBoard[row][col] = firstPlayer; + } else if ((SIZE / 2 - 1 == row || SIZE / 2 == row) + && (col == row)) { + playBoard[row][col] = getEnemy(firstPlayer); + } else { + playBoard[row][col] = null; + } + } + } + return playBoard; + } + + /** + * + * @param self Player + * @return Enemy of the Player + */ + private Player getEnemy(final Player self) { + if (self == human) { + return bot; + } else { + return human; + } + } + + /** + * Switches the currentplayer and checks if the next player can do a valid + * move. If both players cannot move the game is over. + * game is over + */ + private void switchPlayer() { + currentPlayer = getEnemy(currentPlayer); + + if (!movePossible()) { + currentPlayer = getEnemy(currentPlayer); + + if (!movePossible()) { + gameOver = true; + } + } + } + + /** + * Switch a tile to the current player. + * + * @param row Row of field + * @param col Column of field + */ + private void switchTile(final int row, final int col) { + playBoard[row][col] = currentPlayer; + } + + /** + * Switch all corresponding enemy tiles around a field. + * + * @param row Row of field + * @param col Column of field + */ + private void switchTiles(final int row, final int col) { + boolean[] surrounders = validMoves(row, col); + switchTile(row, col); + + for (int i = 0; i < SIZE; i++) { + if (surrounders[i]) { + int x = row + vectors[i].row(); + int y = col + vectors[i].col(); + + while (playBoard[x][y] != currentPlayer) { + switchTile(x, y); + x = x + vectors[i].row(); + y = y + vectors[i].col(); + } + } + } + } + + private int numberOfTiles(Player player) { + int mount = 0; + + for (int row = 0; row < SIZE; row++) { + for (int col = 0; col < SIZE; col++) { + if (playBoard[row][col] == player) { + mount++; + } + } + } + return mount; + } + + /** + * + * @param row Row of the field + * @param col Column of the field + * @return true if field with row and + * col is not set by one of the + * player
else if field is set + */ + private boolean isFree(final int row, final int col) { + if (playBoard[row][col] == null) { + return true; + } + return false; + } + + /** + * Checks the hole playBoard if the current player can do a valid move. + * + * @return true if another move is possible. + */ + private boolean movePossible() { + for (int row = 0; row < SIZE; row++) { + for (int col = 0; col < SIZE; col++) { + + if (isValidMove(row, col)) { + return true; + } + } + } + return false; + } + + /** + * Walk the board in a direction. + * + * @param row Row of field + * @param col Column of field + * @param x row direction to walk + * @param y column direction to walk + * @return if the direction has a valid move + */ + private boolean boardWalker(int row, int col, final int x, final int y) { + row = row + x; + col = col + y; + int counter = 0; + + while (row < SIZE && row >= 0 && col < SIZE && col >= 0) { + if (playBoard[row][col] == null) { + return false; + } else if (playBoard[row][col] == currentPlayer) { + + return (counter > 0); + } + row = row + x; + col = col + y; + counter++; + } + return false; + } + + /** + * Checks a boolean array if it contains a value that is true. + * + * @param array Array + * @return true if any value is true + */ + private boolean checkBoolArray(final boolean[] array) { + for (int i = 0; i < array.length; i++) { + if (array[i]) { + return true; + } + } + return false; + } + + /** + * + * @param row Row of field + * @param col Column of field + * @return if the currentPlayer can do a move on field(row, col) + */ + final boolean isValidMove(final int row, final int col) { + return isFree(row, col) + && checkBoolArray(validMoves(row, col)); + } + + /** + * Searches a field for valid moves. + * + * @param row Row of field + * @param col Column of field + * @return Array of enemy tiles:
+ * array[0] is at 0°, array[1] is diagonal 45°,
+ * array[2] right 90° and so on + */ + private boolean[] validMoves(final int row, final int col) { + boolean[] neighbourhood = new boolean[8]; + + for (int i = 0; i < neighbourhood.length; i++) { + neighbourhood[i] = boardWalker(row, col, + vectors[i].row(), vectors[i].col()); + } + return neighbourhood; + } + + /** + * Switches all tiles and changes switches player if possible. + * + * @param row Row of field + * @param col Column of field + */ + void performMove(final int row, final int col) { + switchTiles(row, col); + switchPlayer(); + + } + + /** + * Calculates the score of a reversi. + * + * @return score + */ + double score() { + double score = (scoreT() + scoreM() + scoreP()); + return score; + } + + /** + * Calculates the weighting score. + * + * @return scoreT + */ + private double scoreT() { + double scoreSelf = 0; + double scoreEnemy = 0; + + for (int row = 0; row < SIZE; row++) { + for (int col = 0; col < SIZE; col++) { + if (playBoard[row][col] == bot) { + scoreSelf += weighting[row][col]; + } else if (playBoard[row][col] == human) { + scoreEnemy += weighting[row][col]; + } + } + } + return (scoreSelf - (1.5 * scoreEnemy)); + } + + /** + * Calculates the mobility score. + * + * @return scoreM + */ + private double scoreM() { + double scoreSelf = 0; + double scoreEnemy = 0; + Player tmp = currentPlayer; + int n = getNumberOfTilesSet(); + + for (int row = 0; row < SIZE; row++) { + for (int col = 0; col < SIZE; col++) { + currentPlayer = bot; + + if (isValidMove(row, col)) { + scoreSelf++; + } + currentPlayer = human; + + if (isValidMove(row, col)) { + scoreEnemy++; + } + currentPlayer = bot; + } + } + currentPlayer = tmp; + return (64.0 / n) * (3.0 * scoreSelf - 4.0 * scoreEnemy); + } + + /** + * Calculates the future-potential score. + * + * @return scoreP + */ + private double scoreP() { + double scoreSelf = 0; + double scoreEnemy = 0; + int n = getNumberOfTilesSet(); + + for (int row = 0; row < SIZE; row++) { + for (int col = 0; col < SIZE; col++) { + if (playBoard[row][col] == human) { + for (int i = 0; i < 8; i++) { + int x = row + vectors[i].row(); + int y = col + vectors[i].col(); + + if ((x >= 0 && x < SIZE && y >= 0 && y < SIZE) + && (playBoard[x][y] == null)) { + scoreSelf++; + } + } + } else if (playBoard[row][col] == bot) { + for (int i = 0; i < 8; i++) { + int x = row + vectors[i].row(); + int y = col + vectors[i].col(); + + if ((x >= 0 && x < SIZE && y >= 0 && y < SIZE) + && (playBoard[x][y] == null)) { + scoreEnemy++; + } + } + } + } + } + return (64.0 / (2 * n)) * (2.5 * scoreSelf - 3.0 * scoreEnemy); + } +} \ No newline at end of file diff --git a/Reversi/ReversiController.java b/Reversi/ReversiController.java new file mode 100644 index 0000000..cad0a28 --- /dev/null +++ b/Reversi/ReversiController.java @@ -0,0 +1,237 @@ +package reversi.controller; + +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Stack; + +import javax.swing.JComboBox; + +import reversi.model.Board; +import reversi.model.Field; +import reversi.model.IllegalMoveException; +import reversi.model.Player; +import reversi.model.Reversi; +import reversi.view.ReversiField; +import reversi.view.ReversiView; + +/** + * Controller has an instance of the board and handles the + * mouse and keyboard actions. Updates the gui if playBoard changes. + */ +public class ReversiController { + + private Player human = new Player(true); + private Player bot = new Player(false); + private Reversi model; + private ReversiView view; + private MachineMoveThread machineMoveThread; + + /** + * Stack for UndoMove method. + */ + private Stack pastMoves; + + /** + * Create the model and the view. + */ + public ReversiController() { + this.model = new Reversi(human, bot, human); + this.view = new ReversiView(); + addListener(); + this.view.updatePlayField(this.model); + createStack(); + } + + /** + * View the gui. + */ + public final void showView() { + this.view.setVisible(true); + } + + /** + * Opens a new reversi game. + * + * @param firstPlayer Player that starts + * @return new reversi board + */ + private Reversi newGame(Player firstPlayer) { + + stopMachineMoveThread(); + createStack(); + Reversi model = new Reversi(this.human, this.bot, firstPlayer); + model.setLevel(this.view.getLevel()); + + if (firstPlayer == bot) { + performMachineMove(model); + } else { + this.view.updatePlayField(model); + this.view.setMovePossible(true); + } + return model; + } + + /** + * Add all Listener in the gui. + */ + private void addListener() { + setPlayFieldListeners(); + this.view.setNewGameListener(new NewGameListener()); + this.view.setSwitchLevelListener(new SwitchLevelListener()); + this.view.setSwitchGameListener(new SwitchGameListener()); + this.view.setUndoMoveListener(new UndoMoveListener()); + this.view.setQuitGameListener(new QuitGameListener()); + } + + /** + * Set the MouseListener for each PlayField. + */ + private void setPlayFieldListeners() { + for (int row = 0; row < Board.SIZE; row++) { + for (int col = 0; col < Board.SIZE; col++) { + this.view.setPlayFieldListener( + new FieldListener(), new Field(row, col)); + } + } + } + + /** + * Init Stack and disable Undo Button. + */ + private void createStack() { + this.pastMoves = new Stack(); + view.setUndoButtonVisible(false); + } + + /** + * Return to the state of the board before the last human and bot move. + */ + private void undoMove() { + stopMachineMoveThread(); + this.model = pastMoves.pop(); + this.view.updatePlayField(this.model); + + if (pastMoves.isEmpty()) { + this.view.setUndoButtonVisible(false); + } else { + this.view.setMovePossible(true); + } + } + + private void performMachineMove(Reversi model) { + this.view.setMovePossible(false); + machineMoveThread = new MachineMoveThread(model, this.view); + machineMoveThread.start(); + } + + private void performHumanMove(Field field) { + pastMoves.push(model.clone()); + view.setUndoButtonVisible(true); + + if (model.move(field.row(), field.col())) { + if (model.next() == human && !model.gameOver()) { + javax.swing.SwingUtilities.invokeLater(new Runnable() { + @Override + public final void run() { + view.viewMessage("The bot has to miss a turn"); + } + }); + } + } else { + undoMove(); + } + + view.updatePlayField(model); + + if (model.next() == bot) { + performMachineMove(model); + } + } + + @SuppressWarnings("deprecation") + private void stopMachineMoveThread() { + if (machineMoveThread != null) { + machineMoveThread.stop(); + } + } + + /** + * Inner class FieldListener performs a move on click and starts the + * machine move. After every move, game over check is done. + */ + private class FieldListener extends MouseAdapter { + @Override + public void mousePressed(final MouseEvent event) { + if (!view.getMovePossible()) { + new IllegalMoveException("Machine Move is active"); + return; + } + + Field field = ((ReversiField) event.getComponent()).getField(); + performHumanMove(field); + } + } + + /** + * Create Listener for the level switch combobox. + */ + private class SwitchLevelListener implements ActionListener { + @Override + public void actionPerformed(final ActionEvent e) { + @SuppressWarnings("unchecked") + JComboBox level = (JComboBox) e.getSource(); + model.setLevel((Integer) level.getSelectedItem()); + } + } + + /** + * Create Listener for the new game button. + */ + private class NewGameListener implements ActionListener { + @Override + public void actionPerformed(final ActionEvent e) { + model = newGame(model.getFirstPlayer()); + } + } + + /** + * Create Listener for the switch game button. + */ + private class SwitchGameListener implements ActionListener { + @Override + public void actionPerformed(final ActionEvent e) { + + if (model.getFirstPlayer() == human) { + model = newGame(bot); + } else { + model = newGame(human); + } + } + } + + /** + * Create Listener for the undo move button. + */ + private class UndoMoveListener implements ActionListener { + @Override + public void actionPerformed(final ActionEvent e) { + undoMove(); + } + } + + /** + * Create Listener for the quit game button. + */ + private class QuitGameListener implements ActionListener { + @Override + public void actionPerformed(final ActionEvent e) { + for (Frame frame : java.awt.Frame.getFrames()) { + frame.dispose(); + } + stopMachineMoveThread(); + } + } +} \ No newline at end of file diff --git a/Reversi/ReversiField.java b/Reversi/ReversiField.java new file mode 100644 index 0000000..fde6546 --- /dev/null +++ b/Reversi/ReversiField.java @@ -0,0 +1,83 @@ +package reversi.view; + + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.event.MouseAdapter; + +import javax.swing.JPanel; + +import reversi.model.Field; +import reversi.model.Player; + +/** + * Class for a single play field panel. Paints the circles with the color of + * the Player. + */ +public class ReversiField extends JPanel { + + private static final long serialVersionUID = -5762403586769495603L; + private int height; + private int width; + private int y; + private int x; + private final Field field; + private Player player; + + /** + * + * @param field Field with coordinates of the play field. + */ + public ReversiField(final Field field) { + this.field = field; + setPlayer(player); + } + + /** + * {@inheritDoc} + */ + @Override + public final void paintComponent(final Graphics g) { + super.paintComponent(g); + + height = (int) (super.getHeight() * 0.8); + width = (int) (super.getWidth() * 0.8); + y = (int) (width * 0.1 + 2); + x = (int) (height * 0.1 + 2); + + if (player != null && player.getType()) { + g.setColor(Color.BLUE); + g.fillOval(y, x, width, height); + } else if (player != null && !player.getType()) { + g.setColor(Color.RED); + g.fillOval(y, x, width, height); + } + } + + /** + * + * @return Field with coordinates of the play field. + */ + public final Field getField() { + return this.field; + } + + /** + * Set the listener created in the controller. + * + * @param ma Mouseadapter + */ + public final void setListener(final MouseAdapter ma) { + this.addMouseListener(ma); + } + + /** + * Set a player on a field (draw the circle in right color). + * + * @param player Player that is set. + */ + public void setPlayer(Player player) { + this.player = player; + repaint(); + } +} \ No newline at end of file diff --git a/Reversi/ReversiGame.java b/Reversi/ReversiGame.java new file mode 100644 index 0000000..84bfdea --- /dev/null +++ b/Reversi/ReversiGame.java @@ -0,0 +1,24 @@ +package reversi.main; + +import reversi.controller.ReversiController; + +/** + * Starts a Reversi Game with the gui. + */ +public final class ReversiGame { + + private ReversiController controller; + + private ReversiGame() { + this.controller = new ReversiController(); + } + + /** + * + * @param args unused... + */ + public static void main(final String...args) { + ReversiGame reversiGame = new ReversiGame(); + reversiGame.controller.showView(); + } +} \ No newline at end of file diff --git a/Reversi/ReversiPlayBoard.java b/Reversi/ReversiPlayBoard.java new file mode 100644 index 0000000..d86a596 --- /dev/null +++ b/Reversi/ReversiPlayBoard.java @@ -0,0 +1,70 @@ +package reversi.view; + +import java.awt.Color; +import java.awt.GridLayout; +import java.awt.event.MouseAdapter; + +import javax.swing.JPanel; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; + +import reversi.model.Board; +import reversi.model.Field; +import reversi.model.Reversi; + +/** + * Create a reversi playboard for the reversi game gui. + */ +public class ReversiPlayBoard extends JPanel { + + private static final long serialVersionUID = 2725183063551812386L; + private ReversiField[][] playField = new ReversiField + [Board.SIZE][Board.SIZE]; + + /** + * Init array with all reversi fields. Set background and tooltip of + * fields. + */ + public ReversiPlayBoard() { + this.setLayout(new GridLayout(Board.SIZE, Board.SIZE)); + this.setBorder(new CompoundBorder( + null, new EmptyBorder(0, 0, 10, 10))); + + for (int row = 0; row < Board.SIZE; row++) { + for (int col = 0; col < Board.SIZE; col++) { + playField[row][col] = new ReversiField(new Field(row, col)); + playField[row][col].setBorder( + new MatteBorder(1, 1, 0, 0, Color.BLACK)); + playField[row][col].setBackground(new Color(0, 170, 0)); + playField[row][col].setToolTipText("Click to make a move!"); + this.add(playField[row][col]); + } + } + this.setVisible(true); + } + + /** + * Update hole playboard players. + * + * @param board Board to update. + */ + public void updatePlayBoard(Reversi board) { + for (int row = 0; row < Board.SIZE; row++) { + for (int col = 0; col < Board.SIZE; col++) { + this.playField[row][col].setPlayer(board.getSlot(row, col)); + } + } + } + + /** + * Set mouselistener of field + * + * @param ma Mouseadapter + * @param field Field + */ + public final void setFieldListener( + final MouseAdapter ma, final Field field) { + this.playField[field.row()][field.col()].setListener(ma); + } +} \ No newline at end of file diff --git a/Reversi/ReversiView.java b/Reversi/ReversiView.java new file mode 100644 index 0000000..d79eee3 --- /dev/null +++ b/Reversi/ReversiView.java @@ -0,0 +1,292 @@ +package reversi.view; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.GridLayout; +import java.awt.Toolkit; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; + +import reversi.model.Board; +import reversi.model.Field; +import reversi.model.Reversi; + +/** + * Create and update the gui. + */ +public class ReversiView extends JFrame { + + private static final long serialVersionUID = 5382834862595792805L; + private boolean movePossible; + private ReversiPlayBoard playBoard; + private JPanel horizTableLabel; + private JPanel verticTableLabel; + private JLabel humanTiles; + private JLabel botTiles; + private JComboBox switchLevel = + new JComboBox(new Integer[] {1, 2, 3, 4, 5}); + private JButton newGame = new JButton("New"); + private JButton switchGame = new JButton("Switch"); + private JButton undoMove = new JButton("Undo"); + private JButton quitGame = new JButton("Quit"); + + /** + * Create the gui for the reversi game. + */ + public ReversiView() { + super("Reversi"); + initForms(); + movePossible = true; + } + + /** + * Create all buttons and the playfield. + */ + private void initForms() { + this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + this.setSize(410, 450); + this.setLayout(new BorderLayout()); + + playBoard = new ReversiPlayBoard(); + verticTableLabel = verticTableLabel(); + horizTableLabel = horizTableLabel(); + humanTiles = tilesSet(); + humanTiles.setForeground(Color.BLUE); + botTiles = tilesSet(); + botTiles.setForeground(Color.RED); + + Container menuBar = new Container(); + menuBar.setLayout(new FlowLayout()); + menuBar.add(humanTiles); + menuBar.add(switchLevel); + menuBar.add(newGame); + menuBar.add(switchGame); + switchLevel.setSelectedIndex(2); + menuBar.add(undoMove); + menuBar.add(quitGame); + menuBar.add(botTiles); + + + this.getContentPane().add(playBoard, BorderLayout.CENTER); + this.getContentPane().add(menuBar, BorderLayout.SOUTH); + this.getContentPane().add(verticTableLabel, BorderLayout.WEST); + this.getContentPane().add(horizTableLabel, BorderLayout.NORTH); + } + + /** + * Create the vertical label for the playfield. + * + * @return JPanel The label. + */ + private JPanel verticTableLabel() { + JPanel panel = new JPanel(); + panel.setLayout(new GridLayout(Board.SIZE, 1)); + panel.setBorder(new CompoundBorder( + null, new EmptyBorder(0, 5, 10, 5))); + + for (int i = 1; i <= Board.SIZE; i++) { + JLabel tmp = new JLabel(String.valueOf(i)); + panel.add(tmp); + } + return panel; + } + + /** + * Create the horizontal label for the playfield. + * + * @return JPanel The label. + */ + private JPanel horizTableLabel() { + JPanel panel = new JPanel(); + panel.setLayout(new GridLayout(1, Board.SIZE)); + panel.setBorder(new CompoundBorder( + null, new EmptyBorder(0, 20, 0, 12))); + + for (int i = 1; i <= Board.SIZE; i++) { + JLabel tmp = new JLabel(String.valueOf(i)); + tmp.setHorizontalAlignment(SwingConstants.CENTER); + panel.add(tmp); + } + return panel; + } + + /** + * Creates both labels for the number of tiles that are set. + * + * @return JLabel The label. + */ + private JLabel tilesSet() { + JLabel tiles = new JLabel(); + tiles.setBorder(new CompoundBorder(null, new EmptyBorder(0, 3, 0, 3))); + tiles.setFont(new Font(Font.DIALOG, Font.BOLD, 20)); + return tiles; + } + + /** + * + * @return Level + */ + public final int getLevel() { + return (Integer) this.switchLevel.getSelectedItem(); + } + + /** + * + * @return true if human is allowed to perform a move.
+ * false e.g. if a machine move is active. + */ + public boolean getMovePossible() { + return movePossible; + } + + /** + * + * @param movePossible Set if human move is possible. + */ + public final void setMovePossible(final boolean movePossible) { + this.movePossible = movePossible; + } + + /** + * + * @param bool Set the undo button vissibility. + */ + public final void setUndoButtonVisible(final boolean bool) { + this.undoMove.setEnabled(bool); + } + + /** + * Update the Tiles display in the gui. + * + * @param human Human amount of tiles. + * @param bot Amount of machine tiles. + */ + private void setTilesSet(int human, int bot) { + this.humanTiles.setText(String.valueOf(human)); + this.botTiles.setText(String.valueOf(bot)); + } + + /** + * Updates the players on the playfield. + * + * @param model Board to update. + */ + public final void updatePlayField(final Reversi model) { + javax.swing.SwingUtilities.invokeLater(new Runnable() { + @Override + public final void run() { + playBoard.updatePlayBoard(model); + setTilesSet(model.getNumberOfHumanTiles(), + model.getNumberOfMachineTiles()); + + /* check movePossible just because of thread interference + when game is over + */ + if (model.gameOver() && movePossible) { + viewMessage(winnerMessage(model)); + movePossible = false; + } + } + }); + } + + /** + * Show a message dialog and play a system sound. + * + * @param msg Message to show. + */ + public final void viewMessage(final String msg) { + Toolkit.getDefaultToolkit().beep(); + JOptionPane.showMessageDialog(this, msg); + } + + /** + * Checks which player has won. + * + * @return Message with the winner. + */ + private String winnerMessage(Reversi model) { + if (model.getWinner() == null) { + return "Tie game!"; + } else if (model.getWinner().getType()) { + return "You have won!"; + } else { + return "The Computer has won!"; + } + } + + /** + * Pass the field listener of the controller to ReversiPlayBoard class. + * + * @param ma Mouseadapter + * @param field Field + */ + public final void setPlayFieldListener( + final MouseAdapter ma, final Field field) { + this.playBoard.setFieldListener(ma, field); + } + + /** + * + * @param l Actionlistener + */ + public final void setSwitchLevelListener(final ActionListener l) { + this.switchLevel.addActionListener(l); + this.switchLevel.setToolTipText("Choose a difficulty level"); + } + + /** + * + * @param l Actionlistener + */ + public final void setNewGameListener(final ActionListener l) { + this.newGame.addActionListener(l); + this.newGame.setMnemonic(KeyEvent.VK_N); + this.newGame.setToolTipText("Start new game"); + } + + /** + * + * @param l Actionlistener + */ + public final void setSwitchGameListener(final ActionListener l) { + this.switchGame.addActionListener(l); + this.switchGame.setMnemonic(KeyEvent.VK_S); + this.switchGame.setToolTipText( + "Switch first player and start new game"); + } + + /** + * + * @param l Actionlistener + */ + public final void setUndoMoveListener(final ActionListener l) { + this.undoMove.addActionListener(l); + this.undoMove.setMnemonic(KeyEvent.VK_U); + this.undoMove.setToolTipText("Undo last move"); + } + + /** + * + * @param l Actionlistener + */ + public final void setQuitGameListener(final ActionListener l) { + this.quitGame.addActionListener(l); + this.quitGame.setMnemonic(KeyEvent.VK_Q); + this.quitGame.setToolTipText("Quit the Game"); + } +} \ No newline at end of file diff --git a/Tries/Node.class b/Tries/Node.class new file mode 100644 index 0000000..b51015d Binary files /dev/null and b/Tries/Node.class differ diff --git a/Tries/Node.java b/Tries/Node.java new file mode 100644 index 0000000..1b5c707 --- /dev/null +++ b/Tries/Node.java @@ -0,0 +1,194 @@ +/** + * Provides methods to manipulate single Nodes.
+ * A Node represents a char in the Trie and provides a children-array + * that saves following chars.
The Node also saves the parent Node. + * if a character is the last one of a string that is set it contains points. + *

i.e. if "hans" is set with 5 points, points + * of 's' is 5. + */ +public class Node { + + /** + * the points of a Student. + */ + private Integer points; + /** + * Node array for children. + */ + private Node[] children = new Node[26]; + /** + * Parent Node. + */ + private Node parent; + /** + * The Character of the Node. + */ + private char ch; + + /** + * Constructor for root Node. + */ + public Node() { + this.points = null; + this.parent = null; + this.ch = '+'; + } + + /** + * Constructor for Node with character ch. + * @param ch character of the Node + */ + private Node(char ch) { + this.points = null; + this.parent = null; + this.ch = ch; + } + + /** + * Constructor for Node chs with known parent + * Node parent. + * @param ch character of the Node + * @param parent Parent Node of the Node + */ + public Node(char ch, Node parent) { + Node hlp = new Node(ch); + hlp.parent = parent; + parent.setChild(ch, hlp); + } + + /** + * Converts a character ch into the index for + * the children array. + * @param ch character to convert + * @return index for the children array + */ + private int convertChar(char ch) { + return ch - 'a'; + } + + /** + * Sets child with character ch + * in children array. + * @param ch the child's character + * @param child the child Node + */ + private void setChild(char ch, Node child) { + this.children[convertChar(ch)] = child; + } + + /** + * Child getter. + * @param ch character of child to get + * @return Node of child + * or null, if child does not exist + */ + public Node getChild(char ch) { + return this.children[convertChar(ch)]; + } + + /** + * Searches the subtree for a string key. + * @param key string to search for + * @return Node of found key or null + */ + public Node find(String key) { + int i = 1; + Node iterator = this.children[convertChar(key.charAt(0))]; + while (iterator != null && i < key.length()) { + iterator = iterator.children[convertChar(key.charAt(i))]; + i++; + } + if (i == key.length()) { + return iterator; + } else { + return null; + } + } + + /** + * Reset points of Node. + */ + public void deleteMe() { + this.points = null; + cleanup(); + } + + /** + * Searches for children. + * @return true, if child was found or
+ * false, if child not available + */ + private boolean hasChild() { + for (int i = 0; i < this.children.length; i++) { + if (this.children[i] != null) { + return true; + } + } + return false; + } + + /** + * Deletes unneeded Nodes after Student deletion. + */ + private void cleanup() { + if (hasChild()) { + return; + } + Node iterator = this.parent; + char isChild = this.ch; + while (iterator.points == null && iterator.parent != null) { + + //delete edge + iterator.children[convertChar(isChild)] = null; + + //if another child is set, the Node is still needed + if (iterator.hasChild()) { + return; + } + isChild = iterator.ch; + iterator = iterator.parent; + } + + //delete child Node in root + iterator.children[convertChar(isChild)] = null; + + } + + /** + * Method to print a Node and his subtree.
+ * Recursive self-calling every child of the Node. + * @return string of Node and subtree + */ + public String toString() { + System.out.print(this.ch); + if (this.points != null) { + System.out.print("[" + this.points + "]"); + } + if (this.hasChild()) { + System.out.print("("); + for (int i = 0; i < this.children.length; i++) { + if (this.children[i] != null) { + this.children[i].toString(); + } + } + System.out.print(")"); + } + return ""; + } + + /** + * Setter for points of Node. + * @param points to set + */ + public void setPoints(Integer points) { + this.points = points; + } + + /** + * Getter for Points of Node. + * @return points of Node + */ + public Integer getPoints() { + return this.points; + } +} \ No newline at end of file diff --git a/Tries/Shell.class b/Tries/Shell.class new file mode 100644 index 0000000..15186cd Binary files /dev/null and b/Tries/Shell.class differ diff --git a/Tries/Shell.java b/Tries/Shell.java new file mode 100644 index 0000000..64d16a6 --- /dev/null +++ b/Tries/Shell.java @@ -0,0 +1,170 @@ +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; + +/** + * Main Class for the Student-Administration.
+ * The user types commands and parameters to manipulate or change + * the Trie.
Commands are found with the HELP command + */ +final class Shell { + private Shell() { } + /** + * Checks if string consists of only chars. + * @param string String to check + * @return true, if string has only chars
+ * false, if string has other signs + */ + private static boolean isCharString(String string) { + for (int j = 0; j < string.length(); j++) { + if (string.charAt(j) - 'a' < 0 + || string.charAt(j) - 'a' > 25) { + return false; + } + } + return true; + } + + /** + * Main method for the Student Administration including the Shell. + * Validation for input. + * @param args are not handled at the moment + * @throws IOException + */ + public static void main(String[] args) throws IOException { + Trie t = new Trie(); + boolean runtime = true; + BufferedReader br = new BufferedReader( + new InputStreamReader(System.in)); + while (runtime) { + System.out.print("trie> "); + String input = br.readLine(); + String[] splited = input.split(" "); + char ch = splited[0].charAt(0); + ch = Character.toLowerCase(ch); + try { + switch(ch) { + case 'n' : + if (splited.length != 1) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + t = new Trie(); + break; + case 'a' : + if (splited.length != 3) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + if (!isCharString(splited[1])) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + if (Integer.parseInt(splited[2]) < 0) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + if (!t.add(splited[1], Integer.parseInt(splited[2]))) { + System.out.println("Error! " + splited[1] + + " is already present."); + } + break; + case 'c' : + if (splited.length != 3) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + if (!isCharString(splited[1])) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + if (Integer.parseInt(splited[2]) < 0) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + if (!t.change(splited[1], Integer.parseInt(splited[2]))) { + System.out.println("Error! " + splited[1] + + " does not yet exist"); + } + break; + case 'd' : + if (splited.length != 2) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + if (!isCharString(splited[1])) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + if (!t.delete(splited[1])) { + System.out.println("Error! " + splited[1] + + " does not yet exist"); + } + break; + case 'p' : + if (splited.length != 2) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + if (!isCharString(splited[1])) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + if (t.points(splited[1]) != null) { + System.out.println(t.points(splited[1])); + } else { + System.out.println("Error! " + splited[1] + + " does not yet exist"); + } + + break; + case 't' : + if (splited.length != 1) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + System.out.println(t.toString()); + break; + case 'h' : + if (splited.length != 1) { + System.out.println("Error! Wrong Parameter! " + + "check HELP"); + break; + } + System.out.println("NEW Creates a new empty Trie "); + System.out.println("ADD adds with "); + System.out.println("CHANGE change of "); + System.out.println("DELETE deletes "); + System.out.println("POINTS displays points of "); + System.out.println("TRIE displays hole Trie"); + System.out.println("HELP display this help"); + System.out.println("QUIT exit Shell"); + break; + case 'q' : + if (splited.length != 1) { + System.out.println("Error! Wrong Parameter! check HELP"); + break; + } + runtime = false; + break; + default : + System.out.println("Error! Wrong command check HELP"); + } + } catch (NumberFormatException e) { + System.out.println("Error! Wrong Parameter! check HELP"); + } + } + } +} \ No newline at end of file diff --git a/Tries/Trie.class b/Tries/Trie.class new file mode 100644 index 0000000..7ccaccf Binary files /dev/null and b/Tries/Trie.class differ diff --git a/Tries/Trie.java b/Tries/Trie.java new file mode 100644 index 0000000..4c9889e --- /dev/null +++ b/Tries/Trie.java @@ -0,0 +1,103 @@ +/** + * Provides methods to manipulate the Trie.
+ * The Trie only saves the root Node of which every Trie-Operation starts. + */ +public class Trie { + + /** + * The root Node of the Trie. + */ + private Node root; + /** + * creates new Trie. + */ + public Trie() { + this.root = new Node(); + } + + /** + * adds new Student key with + * points points to the Trie. + * @param key Student to add + * @param points points of Student + * @return true if add was successful or
+ * false if Student already exists + */ + public boolean add(String key, Integer points) { + Node iterator = this.root; + for (int i = 0; i < key.length(); i++) { + if (iterator.getChild(key.charAt(i)) == null) { + new Node(key.charAt(i), iterator); + } + iterator = iterator.getChild(key.charAt(i)); + } + if (iterator.getPoints() == null) { + iterator.setPoints(points); + return true; + } else { + return false; + } + } + + /** + * deletes Student key in the Trie. + * @param key Student to delete + * @return true if delete was successful or
+ * false> if Student does not exist + */ + public boolean delete(String key) { + Node hlp = this.root.find(key); + if (hlp == null) { + return false; + } + if (hlp.getPoints() != null) { + hlp.deleteMe(); + return true; + } else { + return false; + } + } + + /** + * changes points points of Student key + * in the Trie. + * @param key Student to change + * @param points points of Student to change + * @return true if change was successful or
+ * false if Student does not exist + */ + public boolean change(String key, Integer points) { + Node hlp = this.root.find(key); + if (hlp == null) { + return false; + } + if (hlp.getPoints() != null) { + hlp.setPoints(points); + return true; + } else { + return false; + } + } + + /** + * Getter for points of Student key in the Trie. + * @param key Student to search for + * @return points of Student or
+ * null if Student does not exist + */ + public Integer points(String key) { + Node hlp = this.root.find(key); + if (hlp != null) { + return hlp.getPoints(); + } + return null; + } + + /** + * Method to print the hole Trie. + * @return string of the Trie in the requested format + */ + public String toString() { + return this.root.toString(); + } +} \ No newline at end of file