回答第三部分: 如您所见,Genetic Algorithm 通常不是最难的部分。同样,它是一段简单的代码,实际上是为了练习另一段代码,演员。在这里,actor 在 Shooter 类中实现。这些参与者通常以Turning Machines 的方式建模,因为参与者对一组输入有一组定义的输出。 GA 帮助您确定state table 的最佳配置。在之前对这个问题的回答中,Shooter 实现了一个概率矩阵,就像@SalvadorDali 在他的回答中所描述的那样。
彻底测试之前的Shooter,我们发现它可以做的最好的事情是:
BEST Ave=5, Min=3, Max=9
Best=Shooter:5:[(1,0), (0,0), (2,0), (-1,0), (-2,0), (0,2), (0,1), (0,-1), (0,-2), (0,1)]
这表明击沉一艘 3X3 战列舰平均需要 5 次射击,最少 3 次,最多 9 次。 9 个镜头的位置显示为 X/Y 坐标对。问题“这可以做得更好吗?”取决于人类的聪明才智。遗传算法不能为我们编写新的演员。我想知道 decision tree 是否可以比概率矩阵做得更好,所以我实现了一个来尝试一下:
public class Branch {
private static final int MAX_DEPTH = 10;
private static final int MUTATE_PERCENT = 20;
private Branch hit;
private Branch miss;
private Position shot;
public Branch() {
shot = new Position(
(int)((Math.random()*6.0)-3),
(int)((Math.random()*6.0)-3)
);
}
public Branch(Position shot, Branch hit, Branch miss) {
this.shot = new Position(shot.x, shot.y);
this.hit = null; this.miss = null;
if ( hit != null ) this.hit = hit.clone();
if ( miss != null ) this.miss = miss.clone();
}
public Branch clone() {
return new Branch(shot, hit, miss);
}
public void buildTree(Counter c) {
if ( c.incI1() > MAX_DEPTH ) {
hit = null;
miss = null;
c.decI1();
return;
} else {
hit = new Branch();
hit.buildTree(c);
miss = new Branch();
miss.buildTree(c);
}
c.decI1();
}
public void shoot(Ship ship, Counter c) {
c.incI1();
if ( ship.madeHit(shot)) {
if ( c.incI2() == ship.getSize() ) return;
if ( hit != null ) hit.shoot(ship, c);
}
else {
if ( miss != null ) miss.shoot(ship, c);
}
}
public void mutate() {
if ( (int)(Math.random() * 100.0) < MUTATE_PERCENT) {
shot.x = (int)((Math.random()*6.0)-3);
shot.y = (int)((Math.random()*6.0)-3);
}
if ( hit != null ) hit.mutate();
if ( miss != null ) miss.mutate();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(shot.toString());
if ( hit != null ) sb.append("h:"+hit.toString());
if ( miss != null ) sb.append("m:"+miss.toString());
return sb.toString();
}
}
Branch 类是 node 中的 decision tree (好吧,也许名字不好)。每次击球时,选择的下一个分支取决于击球是否命中。
对射手进行了一些修改以使用新的decisionTree。
public class Shooter implements Comparable<Shooter> {
private Branch decisionTree;
private int aveScore;
// Make a new random decision tree.
public Shooter newShots() {
decisionTree = new Branch();
Counter c = new Counter();
decisionTree.buildTree(c);
return this;
}
// Test this shooter against a ship
public int testShooter(Ship ship) {
Counter c = new Counter();
decisionTree.shoot(ship, c);
return c.i1;
}
// compare this shooter to other shooters, reverse order
@Override
public int compareTo(Shooter o) {
return o.aveScore - aveScore;
}
// mutate this shooter's offspring
public void mutate(Branch pDecisionTree) {
decisionTree = pDecisionTree.clone();
decisionTree.mutate();
}
// min, max, setters, getters
public int getAveScore() {
return aveScore;
}
public void setAveScore(int aveScore) {
this.aveScore = aveScore;
}
public Branch getDecisionTree() {
return decisionTree;
}
@Override
public String toString() {
StringBuilder ret = new StringBuilder("Shooter:"+aveScore+": [");
ret.append(decisionTree.toString());
return ret.append(']').toString();
}
}
细心的读者会注意到,虽然方法本身发生了变化,但Shooter 需要实现的方法与之前的Shooters 没有什么不同。这意味着主要的GA 模拟除了与突变相关的一行之外没有改变,这可能可以解决:
Shooter child = shooters.get(l);
child.mutate( shooters.get(NUM_SHOOTERS - l - 1).getDecisionTree());
典型的模拟运行图现在如下所示:
如您所见,使用Decision Tree 演变的最终最佳平均分数比Probability Matrix 演变的最佳平均分数低一杆。另请注意,这组Shooters 已经花费了大约 800 代来训练到最佳状态,大约是更简单的概率矩阵Shooters 的两倍。最佳决策树Shooter 给出了这样的结果:
BEST Ave=4, Min=3, Max=6
Best=Shooter:4: [(0,-1)h:(0,1)h:(0,0) ... ]
这里,不仅平均出手少了一杆,而且最大出手数比概率矩阵Shooter低1/3。
在这一点上,需要一些非常聪明的人来确定这个演员是否已经达到了问题域的理论最优值,即,这是你试图击沉一艘 3X3 船所能做的最好的事情吗?考虑到这个问题的答案在真正的战舰游戏中会变得更加复杂,因为它有几艘不同大小的战舰。您将如何构建一个演员,将哪些船已经沉没的知识整合到randomly chosen and dynamically modified 的动作中?这就是理解图灵机(也称为 CPU)变得重要的地方。
PS>你还需要这个类:
public class Counter {
int i1;
int i2;
public Counter() {i1=0;i2=0;}
public int incI1() { return ++i1; }
public int incI2() { return ++i2; }
public int decI1() { return --i1; }
public int decI2() { return --i2; }
}