package net.sourceforge.ppf.plugin.countdownplugin; import java.util.HashMap; import java.util.Random; import java.util.Timer; import java.util.TimerTask; import net.sourceforge.ppf.PPF; import net.sourceforge.ppf.PPFPlugin; import net.sourceforge.ppf.util.PPFHelp; ; public class CountdownPlugin extends PPFPlugin { private static final String PLUGIN_VERSION = "1.0"; private static final String PLUGIN_ID = "COUNTDOWN"; static final String commandCountdown = "countdown"; public static final byte NOOP = 0; public static final byte ADD = 1; public static final byte SUBTRACT = 2; public static final byte MULTIPLY = 3; public static final byte DIVIDE = 4; private int _target = 0; private int[] _numbers = new int[0]; private long _startTime = -1; private long _successTime = -1; private long _endTime = -1; private int _solutionCount = 0; private String _solution = null; private int maxNums = 6; private HashMap chosenNumbers = new HashMap(); private HashMap channelCountdowns = new HashMap(); private static final Random random = new Random(); Timer timer; public CountdownPlugin() { // plugin version information setVersion(PLUGIN_VERSION); // build the help for this plugin setHelp(commandCountdown, new PPFHelp(commandCountdown, "Start a countdown number puzzle", PPF.AUTH_NONE)); } // respond to channel messages public void onMessage(String channel, String sender, String login, String hostname, String message) { if(message.equalsIgnoreCase(commandCountdown)) { if(channelCountdowns.containsKey(channel)) { getBot().sendMessage(channel, "There is already a countdown in progress."); } else { int numLarges = random.nextInt(2) + 1; // 1 or 2 large numbers int numSmalls = maxNums - numLarges; // 4 or 5 small numbers int[] numbers = new int[maxNums]; // get random large numbers for(int x = 0; x < numLarges; x++) { int val = (random.nextInt(4) + 1) * 25; if(chosenNumbers.containsKey(val +"")) { x--; } else { numbers[x] = val; chosenNumbers.put(val +"", null); } } // get random small numbers for(int x = numLarges; x < maxNums; x++) { int val = random.nextInt(10) + 1; if(chosenNumbers.containsKey(val +"")) { x--; } else { numbers[x] = val; chosenNumbers.put(val +"", null); } } // get target number int target = random.nextInt(899) + 100; // make number output string StringBuffer outNums = new StringBuffer(); for(int x=0; x < numbers.length; x++) { outNums.append(numbers[x]); if(x < numbers.length-1) { outNums.append(", "); } } getBot().sendMessage(channel, numToWord(numLarges) +" large and "+ numToWord(numSmalls) + " small numbers: "+ outNums.toString() +". Your target is "+ target + ". You have thirty seconds, tick, tock, tick, tock..."); TimedCountdownTask tct = new TimedCountdownTask(channel, numbers, target); timer = new Timer(); timer.schedule(tct, 30 * 1000); // 30 seconds channelCountdowns.put(channel, timer); play(numbers, target); chosenNumbers.clear(); } } } private String numToWord(int num) { if(num == 1) { return "One"; } else if(num == 2) { return "Two"; } else if(num == 4) { return "four"; } else if(num == 5) { return "five"; } return num +""; // fall through } /** * Take an array of numbers and the target to seek. */ public void play(int[] numbers, int target) { _startTime = System.currentTimeMillis(); _target = target; _numbers = numbers; _solutionCount = 0; _solution = null; findSubsets(numbers, new boolean[numbers.length]); _endTime = System.currentTimeMillis(); } /** * Find all subsets of the numbers we have been given before then * using the permutate method to find all permutations of these. */ private void findSubsets(int[] numbers, boolean[] members) { for (int setNum = 0; setNum < Math.pow(2, numbers.length); setNum++) { int size = 0; for (int i = 0; i < numbers.length; i++) { if (members[i]) { size++; } } int[] playset = new int[size]; // fill the array int pos = 0; for (int i = 0; i < numbers.length; i++) { if (members[i]) { playset[pos] = numbers[i]; pos++; } } if (playset.length > 0) { permutate(playset, 0); } // jibble the bits to the next bit pattern int i = numbers.length - 1; while (i >= 0 && members[i]) { members[i] = false; i--; } if (i >= 0) { members[i] = true; } } } /** * Find all permutations of the numbers in the array. * For each permutation, we call the operate method. * (recursive) */ private void permutate(int[] numbers, int k) { if (k == numbers.length) { operate(numbers, new byte[numbers.length - 1], 0); } else { for (int i = k; i < numbers.length; i++) { int temp = numbers[i]; numbers[i] = numbers[k]; numbers[k] = temp; permutate((int[])numbers.clone(), k + 1); } } } /** * Take a permutation of numbers and try all mathematical combinations. * Division is not allowed if there is a non-integral result. * If the target is found, then the solution is printed to the standard * output, using as few brackets as possible. * (recursive) */ private void operate(int[] numbers, byte[] operands, int pos) { if (pos == numbers.length) { // calculate the answer int total = numbers[0]; for (int i = 1; i < numbers.length; i++) { switch (operands[i - 1]) { case ADD: total += numbers[i]; break; case SUBTRACT: total -= numbers[i]; break; case MULTIPLY: total *= numbers[i]; break; case DIVIDE: if (total % numbers[i] != 0) { // Can't allow non-integer divisions in this game! return; } total /= numbers[i]; break; } } if (total == _target) { _solutionCount++; if (_solution == null) { _successTime = System.currentTimeMillis(); StringBuffer solution = new StringBuffer(); byte lastOp = NOOP; for (int i = 0; i < numbers.length - 1; i++) { solution.append(numbers[i]); if (lastOp != NOOP && operands[i] >= 3 && lastOp <= 2) { solution.append(")"); solution.insert(0, "("); } lastOp = operands[i]; switch (operands[i]) { case ADD: solution.append(" + "); break; case SUBTRACT: solution.append(" - "); break; case MULTIPLY: solution.append("*"); break; case DIVIDE: solution.append("/"); break; } } solution.append(numbers[numbers.length - 1]); _solution = solution.toString(); } } } else { if (pos == 0) { operate(numbers, operands, ++pos); } else { int newPos = pos + 1; int previousPos = pos - 1; operands[previousPos] = ADD; operate(numbers, operands, newPos); operands[previousPos] = SUBTRACT; operate(numbers, operands, newPos); operands[previousPos] = MULTIPLY; operate(numbers, operands, newPos); operands[previousPos] = DIVIDE; operate(numbers, operands, newPos); } } } public int getTarget() { return _target; } public int[] getNumbers() { return _numbers; } public long getStartTime() { return _startTime; } public long getSuccessTime() { return _successTime; } public long getEndTime() { return _endTime; } public int getSolutionCount() { return _solutionCount; } public String getSolution() { return _solution; } class TimedCountdownTask extends TimerTask { private String channel; private int[] numbers; private int target; public TimedCountdownTask(String channel, int[] numbers, int target) { this.channel = channel; this.numbers = numbers; this.target = target; } public void run() { getBot().sendMessage(channel, "Duh dum, duh dum, duh-da-da-dum! Your 30 seconds are up!"); long t1 = getSuccessTime() - getStartTime(); long t2 = getEndTime() - getStartTime(); if(getSolutionCount() > 0) { getBot().sendMessage(channel, "I found a solution in approximately "+ t1 + " milliseconds. I found all "+ t2 +" solutions in about "+ getSolutionCount() +" milliseconds."); } else { getBot().sendMessage(channel, "Oh dear, I can't solve that one."); } channelCountdowns.remove(channel); } } }