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);
}
}
}