Use new AI for computer players

git-svn-id: svn://sunsvr01.isp.uni-luebeck.de/swproj13/trunk@448 72836036-5685-4462-b002-a69064685172
This commit is contained in:
Jannis Harder 2011-06-17 22:12:41 +02:00
parent a07e723242
commit 47bf19036e
5 changed files with 220 additions and 192 deletions

View file

@ -27,9 +27,10 @@ public class TurnLogic {
private State top;
private volatile boolean abort = false;
private volatile boolean autoAbort = false;
private int neededPoints = 0;
private double neededScore = Double.NEGATIVE_INFINITY;
private double neededScore = 0;
@SuppressWarnings("serial")
private static class Contradiction extends Throwable {
@ -548,6 +549,10 @@ public class TurnLogic {
public List<StoneSet> getResult() {
State result = bestState;
if (result == null) {
return null;
}
ArrayList<StoneSet> outputSets = new ArrayList<StoneSet>();
for (int i = 0; i < stoneCount; i++) {
@ -560,9 +565,6 @@ public class TurnLogic {
&& isNullSet(stone.rightGroup)) {
break;
}
/* System.out.println("--");
System.out.println(stone.rightRun);
System.out.println(stone.rightGroup); */
Integer rightRunID = stone.rightRun.iterator().next();
Integer rightGroupID = stone.rightGroup.iterator().next();
@ -600,7 +602,7 @@ public class TurnLogic {
}
public double optimize() {
while (solve()) {
while (!autoAbort && solve()) {
neededScore = top.getScore();
}
return neededScore;
@ -688,4 +690,12 @@ public class TurnLogic {
// This should never happen
throw new Error("Internal AI error");
}
public void autoAbort() {
if (bestState != null) {
abort = true;
}
autoAbort = true;
}
}

View file

@ -0,0 +1,197 @@
package jrummikub.control.turn;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import jrummikub.ai.TurnLogic;
import jrummikub.control.AIUtil;
import jrummikub.model.Position;
import jrummikub.model.Stone;
import jrummikub.model.StoneColor;
import jrummikub.model.StoneSet;
import jrummikub.util.Pair;
/**
* Base class for AI players
*
*/
public class AIControl extends AbstractTurnControl {
private TurnLogic logic;
private boolean turnDone = false;
boolean useBackgroundThread = true;
long startTime;
@Override
public void startTurn() {
timer.startTimer();
startTime = System.currentTimeMillis();
compute();
}
protected void timeOut() {
executeTurn();
}
@Override
protected void cleanUp() {
if (logic != null)
logic.abort();
turnDone = true;
super.cleanUp();
}
private void emitEndOfTurn() {
turnDone = true;
long turnLength = System.currentTimeMillis() - startTime;
if (useBackgroundThread) {
Timer timer = new Timer(Math.max(0, (int) (1000 + Math.random() * 2000 - turnLength)),
new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
cleanUp();
endOfTurnEvent.emit();
}
});
timer.setRepeats(false);
timer.start();
} else {
cleanUp();
endOfTurnEvent.emit();
}
}
private void compute() {
switch (turnInfo.getTurnMode()) {
case MAY_REDEAL:
emitEndOfTurn();
break;
case INSPECT_ONLY:
emitEndOfTurn();
break;
case NORMAL_TURN:
turn();
break;
}
}
private void turn() {
List<Stone> tableStones = new ArrayList<Stone>();
List<Stone> handStones = new ArrayList<Stone>();
for (Pair<Stone, Position> entry : turnInfo.getHand()) {
handStones.add(entry.getFirst());
}
if (turnInfo.getLaidOut()) {
for (Pair<StoneSet, Position> entry : turnInfo.getTable()) {
for (Stone stone : entry.getFirst()) {
tableStones.add(stone);
}
}
}
logic = new TurnLogic(settings, tableStones, handStones);
if (!turnInfo.getLaidOut()) {
logic.needIntialMeldThreshold();
}
if (useBackgroundThread) {
Timer timer = new Timer(10000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
logic.autoAbort();
}
});
timer.setRepeats(false);
timer.start();
Thread computeThread = new Thread(new Runnable() {
@Override
public void run() {
logic.optimize();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
executeTurn();
}
});
}
});
computeThread.start();
} else {
logic.optimize();
executeTurn();
}
}
private void executeTurn() {
if (turnDone) {
return;
}
List<StoneSet> result = logic.getResult();
if (result != null) {
if (turnInfo.getLaidOut()) {
outerLoop: for (Iterator<Pair<StoneSet, Position>> it = turnInfo
.getTable().iterator(); it.hasNext();) {
Pair<StoneSet, Position> pair = it.next();
setSearch: for (Iterator<StoneSet> it2 = result.iterator(); it2
.hasNext();) {
StoneSet set = it2.next();
if (set.getSize() != pair.getFirst().getSize()) {
continue;
}
for (int i = 0; i < set.getSize(); i++) {
if (set.get(i) != pair.getFirst().get(i)) {
continue setSearch;
}
}
it2.remove();
continue outerLoop;
}
it.remove();
}
}
for (StoneSet set : result) {
turnInfo.getTable().drop(
set,
new Position(10 * (Math.random() * 2 - 1), 5 * (Math
.random() * 2 - 1)));
for (Stone stone : set) {
turnInfo.getHand().pickUp(stone);
}
}
}
emitEndOfTurn();
}
/**
* Get the factory for the base AI control
*
* @return the factory
*/
static public TurnControlFactory getFactory() {
return new TurnControlFactory() {
@Override
public ITurnControl create() {
return new AIControl();
}
};
}
}

View file

@ -1,174 +0,0 @@
package jrummikub.control.turn;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import javax.swing.SwingUtilities;
import jrummikub.control.AIUtil;
import jrummikub.model.Position;
import jrummikub.model.Stone;
import jrummikub.model.StoneColor;
import jrummikub.model.StoneSet;
import jrummikub.util.Pair;
/**
* Base class for AI players
*
*/
public class BaseAIControl extends AbstractTurnControl {
private long startTime;
private Thread computeThread;
private volatile boolean stopRunning = false;
@Override
public void startTurn() {
computeThread = new Thread(new Runnable() {
@Override
public void run() {
compute();
}
});
startTime = System.currentTimeMillis();
timer.startTimer();
computeThread.start();
}
protected void timeOut() {
cleanUp();
endOfTurnEvent.emit();
}
protected void cleanUp() {
super.cleanUp();
stopRunning = true;
}
private void compute() {
switch (turnInfo.getTurnMode()) {
case MAY_REDEAL:
emitRedeal();
break;
case INSPECT_ONLY:
emitEndOfTurn();
break;
case NORMAL_TURN:
turn();
break;
}
}
private Stone findMatchingStone(Stone target) {
for (Pair<Stone, Position> entry : turnInfo.getHand()) {
Stone stone = entry.getFirst();
if (stone.getValue() == target.getValue()
&& stone.getColor() == target.getColor()) {
return stone;
}
}
for (Pair<Stone, Position> entry : turnInfo.getHand()) {
Stone stone = entry.getFirst();
if (stone.isJoker()) {
return stone;
}
}
return null;
}
private Stone pickUpMatchingStone(Stone target) {
Stone match = findMatchingStone(target);
turnInfo.getHand().pickUp(match);
return match;
}
private void turn() {
List<Stone> stones = new ArrayList<Stone>();
for (Pair<Stone, Position> entry : turnInfo.getHand()) {
stones.add(entry.getFirst());
}
Pair<TreeMap<Pair<Integer, StoneColor>, Integer>, Integer> counts = AIUtil
.countStones(stones);
AIUtil aiUtil = new AIUtil(settings);
Pair<List<StoneSet>, Integer> result = aiUtil.findSetsWithTotalPoints(
Math.max(30, settings.getInitialMeldThreshold() * 2),
counts.getFirst(), counts.getSecond());
if (!turnInfo.getLaidOut()
&& result.getSecond() < settings.getInitialMeldThreshold()) {
emitEndOfTurn();
return;
}
for (StoneSet set : result.getFirst()) {
List<Stone> handStones = new ArrayList<Stone>();
for (Stone stone : set) {
handStones.add(pickUpMatchingStone(stone));
}
turnInfo.getTable().drop(
new StoneSet(handStones),
new Position((float) Math.random() * 30 - 15,
(float) Math.random() * 6 - 3));
}
emitEndOfTurn();
}
private void emitRedeal() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (!stopRunning) {
cleanUp();
redealEvent.emit();
}
}
});
}
private void emitEndOfTurn() {
long timeElapsed = System.currentTimeMillis() - startTime;
long timeNeeded = Math.min((long) (1000 + Math.random()
* turnInfo.getHand().getSize() * 100), 50000);
long waitTime = timeNeeded - timeElapsed;
if (waitTime > 0) {
try {
Thread.sleep(waitTime);
} catch (InterruptedException e) {
// This shouldn't happen
}
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (!stopRunning) {
cleanUp();
endOfTurnEvent.emit();
}
}
});
}
/**
* Get the factory for the base AI control
*
* @return the factory
*/
static public TurnControlFactory getFactory() {
return new TurnControlFactory() {
@Override
public ITurnControl create() {
return new BaseAIControl();
}
};
}
}

View file

@ -27,7 +27,7 @@ public abstract class TurnControlFactory {
case HUMAN:
return HumanTurnControl.getFactory();
case COMPUTER:
return BaseAIControl.getFactory();
return AIControl.getFactory();
}
return null;
}

View file

@ -22,7 +22,7 @@ import org.junit.Before;
import org.junit.Test;
/** */
public class BaseAIControlTest {
public class AIControlTest {
ITurnControl aiControl;
GameSettings gameSettings;
@ -38,6 +38,7 @@ public class BaseAIControlTest {
@Before
public void setUp() {
aiControl = TurnControlFactory.getFactory(Type.COMPUTER).create();
((AIControl)aiControl).useBackgroundThread = false;
gameSettings = new GameSettings();
playerSettings = new PlayerSettings("ROBOT_01", Color.GRAY);
player = new Player(playerSettings);
@ -74,14 +75,12 @@ public class BaseAIControlTest {
* @throws InterruptedException
*/
@Test(timeout = 10000)
public void testTurnZeroRedealing() throws InterruptedException {
public void testTurnZeroNoRedealing() throws InterruptedException {
aiControl.setup(new ITurnControl.TurnInfo(table, player.getHand(),
player.getLaidOut(), TurnMode.MAY_REDEAL), gameSettings, view);
aiControl.startTurn();
while (!redealt) {
Thread.sleep(100);
}
assertFalse(turnEnded);
assertTrue(turnEnded);
assertFalse(redealt);
assertEquals(table.getSize(), 0);
}
@ -95,9 +94,7 @@ public class BaseAIControlTest {
player.getLaidOut(), TurnMode.INSPECT_ONLY),
gameSettings, view);
aiControl.startTurn();
while (!turnEnded) {
Thread.sleep(100);
}
assertTrue(turnEnded);
assertFalse(redealt);
assertEquals(table.getSize(), 0);
}
@ -110,9 +107,7 @@ public class BaseAIControlTest {
aiControl.setup(new ITurnControl.TurnInfo(table, player.getHand(),
player.getLaidOut(), TurnMode.NORMAL_TURN), gameSettings, view);
aiControl.startTurn();
while (!turnEnded) {
Thread.sleep(100);
}
assertTrue(turnEnded);
assertFalse(redealt);
assertEquals(table.getSize(), 1);
assertEquals(player.getHand().getSize(), 0);