/** * Handles a combinatorial game match between two players. * * @author Kyle Burke <paithanq@gmail.com> */ import java.lang.*; import java.io.*; import java.util.*; import java.util.concurrent.Callable; import java.text.NumberFormat; public class Referee<Game extends CombinatorialGame> implements Callable<Integer> { //instance variables //two players. players[0] will be left; players[1] will be right. protected ArrayList<Player<Game>> players; //the number of games each player has forfeited private ArrayList<Integer> forfeitsByPlayer; //current game state protected Game position; //starting game state protected PositionFactory<Game> startStateGenerator; //current player protected int currentPlayer; //time to delay between player turns private int delay; //number of attempts each player gets to choose a proper move private int numMoveAttempts; //output controller for this. private Display display; //display controller. Implements State Pattern. private class Display { //whether this prints to the screen private boolean prints; //constructor public Display(boolean prints) { this.prints = prints; } //print out public void println(String string) { if (this.prints) { System.out.println(string); } } } //constructors //private constructor protected Referee(Player<Game> leftPlayer, Player<Game> rightPlayer) { this.players = new ArrayList<Player<Game>>(); players.add(leftPlayer); players.add(rightPlayer); this.forfeitsByPlayer = new ArrayList<Integer>(); this.forfeitsByPlayer.add(0); this.forfeitsByPlayer.add(0); this.setDelay(3000); this.setPrint(true); this.setAttempts(1); } /** * Class constructor. * * @param players Array of two players. * @param stateGenerator Generator of states. */ public Referee(Player<Game> leftPlayer, Player<Game> rightPlayer, PositionFactory<Game> stateGenerator) { this(leftPlayer, rightPlayer); this.startStateGenerator = stateGenerator; } /** * Class constructor. * * @param players Array of two players. * @param initialPosition The position the Referee will always start from. */ public Referee(Player<Game> leftPlayer, Player<Game> rightPlayer, Game initialPosition) { this(leftPlayer, rightPlayer); final Game startingPosition = initialPosition; this.startStateGenerator = new PositionFactory<Game>() { private Game position = startingPosition; public Game getPosition() { return (Game) position.clone(); } }; } /** * Gets a String representation. * * @return A String representation of this. */ public String toString() { String string = "A referee between two players."; return string; } /** * Sets the delay between turns. * * @param delay The millisecond delay between turns. */ public void setDelay(int delay) { this.delay = delay; } /** * Returns the delay. * * @return The milliseconds this waits between asking for moves. */ public int getDelay() { return this.delay; } /** * Sets the number of attempts this will tolerate per turn. * * @param attempts The number of times a player can return an incorrect move before we choose a random one for them! */ public void setAttempts(int attempts) { this.numMoveAttempts = attempts; } /** * Sets whether this prints output. * * @param doesPrint Whether this will print to System.out. */ public void setPrint(boolean doesPrint) { this.display = new Display(doesPrint); } /** * Runs the competition. * * @return Index of the winning player. */ public Integer call() { int startingPlayer = (int) (Math.floor(Math.random()*2)); return (Integer) this.call(startingPlayer); } /** * Gets a player's name. * * @param playerId The index of the sought player. * @return The name of the chosen player, as defined by the player's toString() method. */ public String getPlayerName(int playerId) { return this.players.get(playerId).toString(); } /** * Gets a player's role. * * @param playerId The index of the sought player. * @return The name of the role of the chosen player. E.g. Blue vs. Red, Left vs Right, etc. */ public String getPlayerRole(int playerId) { return this.position.getPlayerName(playerId); } /** * Gets the current player's name. * * @return The result of calling the toString() method on the current player. */ public String getCurrentPlayerName() { return this.getPlayerName(this.currentPlayer); } /** * Gets the current player's role. * * @return A String with the current player's role. E.g. "Left" or "Right", "Blue" or "Red", etc. */ public String getCurrentPlayerRole() { return this.getPlayerRole(this.currentPlayer); } /** * Plays games until either there is one forfeit or the given number of games ends. */ public void playToForfeit(int maxGames) { for (int i = 0; i < maxGames; i++) { System.out.println("Starting Game #" + i + "..."); this.call(); if (this.getTotalForfeits() > 0) { System.out.println("Reached a forfeit!!"); break; } } } //starts the game, with a specified initial player private int call(int startingPlayer) { this.currentPlayer = startingPlayer; this.position = this.startStateGenerator.getPosition(); this.display.println("Let's get ready to rumble!"); for (int i = 0; i < 2; i++) { this.display.println("In this corner ... playing as " + this.getPlayerRole(i) + " ... " + this.getPlayerName(i) + "!"); } this.display.println("Starting board:\n" + this.position); this.display.println(this.getCurrentPlayerName() + " will start us off. Begin!"); return this.requestMoves(); } //asks for a move from one person protected Game getNextMove() { Game option; int attemptsRemaining = this.numMoveAttempts; while (attemptsRemaining > 0) { try { option = this.players.get(this.currentPlayer).getMove((Game) this.position.clone(), this.currentPlayer); if (this.position.hasOption(this.currentPlayer, option)) { return option; } else { this.display.println("Player " + this.getCurrentPlayerName() + " (" + this.getCurrentPlayerRole() + ") tried to move from \n" + this.position + "\n to \n" + option + ", which is not a legal option. They forfeit the game!"); throw new RuntimeException(this.getCurrentPlayerRole() + " tried to move from \n" + this.position + "\n to \n" + option + ", which is not a legal option. They forfeit the game!"); } } catch (NoSuchElementException nsee) { this.display.println("We experienced a problem! A player is telling us that there is no option for " + this.getCurrentPlayerRole() + " from position " + this.position + "\nThat can't be right! Something fishy is going on here!"); } attemptsRemaining --; this.display.println(this.getCurrentPlayerName() + " has " + attemptsRemaining + " tries left."); try { Thread.sleep(this.delay); } catch (Exception e) { this.display.println("Couldn't sleep!"); } } this.display.println("Choosing a random move for " + this.players.get(this.currentPlayer) + " instead!"); //get a random option Random randomGenerator = new Random(); Collection<Game> optionCollection = (Collection<Game>) this.position.getOptions(this.currentPlayer); Object[] possibleOptions = optionCollection.toArray(); Object randomOption = possibleOptions[randomGenerator.nextInt(possibleOptions.length)]; return (Game) randomOption; } //determines whether the next player has any moves protected boolean movesExist() { return this.position.playerHasAnOption(this.currentPlayer); } //uses the display to print a statement protected void printLine(String s) { this.display.println(s); } //moves to a new game //does not test that option is legal! Should already have been tested! protected void move(Game option) { this.position = option; this.display.println(this.getCurrentPlayerName() + " (" + this.getCurrentPlayerRole() + ") moved to \n" + this.position); this.currentPlayer += 1; if (this.currentPlayer == this.players.size()) { this.currentPlayer = 0; } } //returnts the total number of forfeits private int getTotalForfeits() { return this.forfeitsByPlayer.get(0) + this.forfeitsByPlayer.get(1); } //performs a forfeit for a player protected void currentPlayerForfeit() { int previousForfeits = this.forfeitsByPlayer.get(this.currentPlayer); if (previousForfeits == 0) { //print out some information about this forfeit System.out.println(this.getCurrentPlayerName() + " forfeited while trying to make a move on: " + this.position); } this.forfeitsByPlayer.set(this.currentPlayer, previousForfeits + 1); } // repeatedly asks for moves until someone loses. // (This is the main loop for this code) protected int requestMoves() { while (this.position.playerHasAnOption(this.currentPlayer)) { try { Thread.sleep(this.delay); } catch (Exception e) { this.display.println("Couldn't sleep!"); } try { Game option = this.getNextMove(); this.move(option); } catch (Exception e) { int errorLine = -1; String className = ""; StackTraceElement[] stackFrames = e.getStackTrace(); for (StackTraceElement frame : stackFrames) { errorLine = frame.getLineNumber(); className = frame.getClassName(); if (errorLine != -1) break; } this.display.println("A problem occurred (" + e.toString() + ") in " + className + " on line " + errorLine + " while " + this.getCurrentPlayerName() + " was taking their turn. The other player wins by default!"); e.printStackTrace(); return 1 - this.currentPlayer; } } try { Thread.sleep(this.delay); } catch (Exception e) { this.display.println("Couldn't sleep!"); } int winningPlayer = 1 - this.currentPlayer; this.display.println("There are no options for " + this.getCurrentPlayerName() + "! " + this.getPlayerName(winningPlayer) + " wins!\nCongratulations to " + this.getPlayerName(winningPlayer) + "!"); return winningPlayer; } //adds a forfeiture to one of the players protected void forfeit(int playerId) { this.forfeitsByPlayer.set(this.currentPlayer, this.forfeitsByPlayer.get(this.currentPlayer) + 1); } /** * Pits two players against each other multiple times, while subduing print statements mid-tournament. * * @param numGames The number of games in the competition. * @return An Array of doubles. The zeroeth element is the percentage Left won, oneth the percentage Right won, twoeth the number of total forfeits. */ public double[] gauntlet(int numGames) { return this.gauntlet(numGames, false); } /** * Pits two players against each other multiple times. * * @param numGames The number of games in the competition. * @param printDetails Whether or not to print details about each game. * @return An Array of doubles. The zeroeth element is the percentage Left won, oneth the percentage Right won, twoeth the number of total forfeits. */ public double[] gauntlet(int numGames, boolean printDetails) { this.forfeitsByPlayer.set(0, 0); this.forfeitsByPlayer.set(1, 0); int[] gamesWon = new int[] {0, 0}; int winner; this.setPrint(true); this.display.println("Beginning the competition! There will be " + numGames + " games played!"); this.setPrint(printDetails); this.setDelay(0); for (int gameIndex = 0; gameIndex < numGames; gameIndex ++) { winner = this.call(gameIndex % 2); gamesWon[winner] ++; } this.setPrint(true); this.display.println("Competition complete! Games won:"); double[] percentagesWon = new double[] {((double) gamesWon[0]) / numGames, ((double) gamesWon[1]) / numGames, (double) this.getTotalForfeits()}; NumberFormat percentFormat = NumberFormat.getPercentInstance(); for (int i = 0; i < 2; i ++) { this.display.println(" " + this.getPlayerName(i) + " (" + this.getPlayerRole(i) + ") : " + gamesWon[i] + " (" + percentFormat.format(((double) gamesWon[i]) / numGames) + ") forfeits: " + this.forfeitsByPlayer.get(i)); } return percentagesWon; } //main method for testing public static void main(String[] args) { //TODO: need to add a unit test that works for any type of game. Unfortunately, no such thing exists... } } //end of Referee.java