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 State top;
|
||||||
|
|
||||||
private volatile boolean abort = false;
|
private volatile boolean abort = false;
|
||||||
|
private volatile boolean autoAbort = false;
|
||||||
|
|
||||||
private int neededPoints = 0;
|
private int neededPoints = 0;
|
||||||
private double neededScore = Double.NEGATIVE_INFINITY;
|
private double neededScore = 0;
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
private static class Contradiction extends Throwable {
|
private static class Contradiction extends Throwable {
|
||||||
|
@ -548,6 +549,10 @@ public class TurnLogic {
|
||||||
public List<StoneSet> getResult() {
|
public List<StoneSet> getResult() {
|
||||||
State result = bestState;
|
State result = bestState;
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
ArrayList<StoneSet> outputSets = new ArrayList<StoneSet>();
|
ArrayList<StoneSet> outputSets = new ArrayList<StoneSet>();
|
||||||
|
|
||||||
for (int i = 0; i < stoneCount; i++) {
|
for (int i = 0; i < stoneCount; i++) {
|
||||||
|
@ -560,9 +565,6 @@ public class TurnLogic {
|
||||||
&& isNullSet(stone.rightGroup)) {
|
&& isNullSet(stone.rightGroup)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* System.out.println("--");
|
|
||||||
System.out.println(stone.rightRun);
|
|
||||||
System.out.println(stone.rightGroup); */
|
|
||||||
Integer rightRunID = stone.rightRun.iterator().next();
|
Integer rightRunID = stone.rightRun.iterator().next();
|
||||||
Integer rightGroupID = stone.rightGroup.iterator().next();
|
Integer rightGroupID = stone.rightGroup.iterator().next();
|
||||||
|
|
||||||
|
@ -600,7 +602,7 @@ public class TurnLogic {
|
||||||
}
|
}
|
||||||
|
|
||||||
public double optimize() {
|
public double optimize() {
|
||||||
while (solve()) {
|
while (!autoAbort && solve()) {
|
||||||
neededScore = top.getScore();
|
neededScore = top.getScore();
|
||||||
}
|
}
|
||||||
return neededScore;
|
return neededScore;
|
||||||
|
@ -688,4 +690,12 @@ public class TurnLogic {
|
||||||
// This should never happen
|
// This should never happen
|
||||||
throw new Error("Internal AI error");
|
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:
|
case HUMAN:
|
||||||
return HumanTurnControl.getFactory();
|
return HumanTurnControl.getFactory();
|
||||||
case COMPUTER:
|
case COMPUTER:
|
||||||
return BaseAIControl.getFactory();
|
return AIControl.getFactory();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
public class BaseAIControlTest {
|
public class AIControlTest {
|
||||||
ITurnControl aiControl;
|
ITurnControl aiControl;
|
||||||
|
|
||||||
GameSettings gameSettings;
|
GameSettings gameSettings;
|
||||||
|
@ -38,6 +38,7 @@ public class BaseAIControlTest {
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
aiControl = TurnControlFactory.getFactory(Type.COMPUTER).create();
|
aiControl = TurnControlFactory.getFactory(Type.COMPUTER).create();
|
||||||
|
((AIControl)aiControl).useBackgroundThread = false;
|
||||||
gameSettings = new GameSettings();
|
gameSettings = new GameSettings();
|
||||||
playerSettings = new PlayerSettings("ROBOT_01", Color.GRAY);
|
playerSettings = new PlayerSettings("ROBOT_01", Color.GRAY);
|
||||||
player = new Player(playerSettings);
|
player = new Player(playerSettings);
|
||||||
|
@ -74,14 +75,12 @@ public class BaseAIControlTest {
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
@Test(timeout = 10000)
|
@Test(timeout = 10000)
|
||||||
public void testTurnZeroRedealing() throws InterruptedException {
|
public void testTurnZeroNoRedealing() throws InterruptedException {
|
||||||
aiControl.setup(new ITurnControl.TurnInfo(table, player.getHand(),
|
aiControl.setup(new ITurnControl.TurnInfo(table, player.getHand(),
|
||||||
player.getLaidOut(), TurnMode.MAY_REDEAL), gameSettings, view);
|
player.getLaidOut(), TurnMode.MAY_REDEAL), gameSettings, view);
|
||||||
aiControl.startTurn();
|
aiControl.startTurn();
|
||||||
while (!redealt) {
|
assertTrue(turnEnded);
|
||||||
Thread.sleep(100);
|
assertFalse(redealt);
|
||||||
}
|
|
||||||
assertFalse(turnEnded);
|
|
||||||
assertEquals(table.getSize(), 0);
|
assertEquals(table.getSize(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,9 +94,7 @@ public class BaseAIControlTest {
|
||||||
player.getLaidOut(), TurnMode.INSPECT_ONLY),
|
player.getLaidOut(), TurnMode.INSPECT_ONLY),
|
||||||
gameSettings, view);
|
gameSettings, view);
|
||||||
aiControl.startTurn();
|
aiControl.startTurn();
|
||||||
while (!turnEnded) {
|
assertTrue(turnEnded);
|
||||||
Thread.sleep(100);
|
|
||||||
}
|
|
||||||
assertFalse(redealt);
|
assertFalse(redealt);
|
||||||
assertEquals(table.getSize(), 0);
|
assertEquals(table.getSize(), 0);
|
||||||
}
|
}
|
||||||
|
@ -110,9 +107,7 @@ public class BaseAIControlTest {
|
||||||
aiControl.setup(new ITurnControl.TurnInfo(table, player.getHand(),
|
aiControl.setup(new ITurnControl.TurnInfo(table, player.getHand(),
|
||||||
player.getLaidOut(), TurnMode.NORMAL_TURN), gameSettings, view);
|
player.getLaidOut(), TurnMode.NORMAL_TURN), gameSettings, view);
|
||||||
aiControl.startTurn();
|
aiControl.startTurn();
|
||||||
while (!turnEnded) {
|
assertTrue(turnEnded);
|
||||||
Thread.sleep(100);
|
|
||||||
}
|
|
||||||
assertFalse(redealt);
|
assertFalse(redealt);
|
||||||
assertEquals(table.getSize(), 1);
|
assertEquals(table.getSize(), 1);
|
||||||
assertEquals(player.getHand().getSize(), 0);
|
assertEquals(player.getHand().getSize(), 0);
|
Reference in a new issue