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:
parent
a07e723242
commit
47bf19036e
5 changed files with 220 additions and 192 deletions
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
197
src/jrummikub/control/turn/AIControl.java
Normal file
197
src/jrummikub/control/turn/AIControl.java
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -27,7 +27,7 @@ public abstract class TurnControlFactory {
|
|||
case HUMAN:
|
||||
return HumanTurnControl.getFactory();
|
||||
case COMPUTER:
|
||||
return BaseAIControl.getFactory();
|
||||
return AIControl.getFactory();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -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);
|
Reference in a new issue