最近我想教我的城堡游戏的AI如何使用我添加到游戏中的咒语。你可以在这里试试这个游戏:铸铁件
一开始,我想我必须为四个可玩的种族中的每一个添加子类,以便说明每个种族都有不同的法术。然而,我最终想出了一种更通用的方法,它也同样有效。
我正在为人工智能使用gdx-ai库。最初(在咒语添加之前),AI的核心逻辑如下:
@Override
public void update(AIPlayerConquer entity) {
//this is the hierarchy of action priorities
//when idle, this will be called to search for what state to select next
//shoot ogres first because they will destroy walls
entity.findPersons();
if (entity.shouldAttackPersons()) {
entity.stateMachine.changeState(SHOOT_PERSONS);
}
//shoot if the opponent is aggressive or controls a lot of the map
else if (entity.isOpponentAggressive() ||
entity.shouldAttackBasedOnOpponentPercentage() ||
entity.shouldAttackBasedOnPercentOwned()) {
entity.stateMachine.changeState(SHOOT);
//otherwise try to build
} else {
entity.stateMachine.changeState(BUILD);
}
}只要做一些小小的改动,我就在核心逻辑中添加了咒语。我想把法术放在比常规动作更重要的位置,因为它们使游戏更令人兴奋。以下是全班同学:
public enum AIPlayerConquerState implements State<AIPlayerConquer> {
IDLE,
SHOOT {
@Override
public void enter(AIPlayerConquer entity) {
entity.findOpponentWallTiles();
if (!entity.doesOpponentHaveWalls()) {
entity.stateMachine.changeState(BUILD);
} else {
entity.angryAsEnemy();
entity.tryToShootWalls();
}
entity.stateMachine.changeState(IDLE);
}
},
SHOOT_PERSONS {
@Override
public void enter(AIPlayerConquer entity) {
entity.findPersons();
if (!entity.shouldAttackPersons()) {
entity.stateMachine.changeState(BUILD);
} else {
entity.tryToShootPersons();
}
entity.stateMachine.changeState(IDLE);
}
},
BUILD {
@Override
public void enter(AIPlayerConquer entity) {
entity.buildingAsEnemy();
entity.doActionForConquerGame();
entity.stateMachine.changeState(IDLE);
}
},
AGGRESIVE_SPELL {
@Override
public void enter(AIPlayerConquer entity) {
entity.tryToCastOffensiveSpell();
entity.stateMachine.changeState(IDLE);
}
},
DEFENSIVE_SPELL {
@Override
public void enter(AIPlayerConquer entity) {
entity.tryToCastDefensiveSpell();
entity.stateMachine.changeState(IDLE);
}
};
@Override
public void update(AIPlayerConquer entity) {
//this is the hierarchy of action priorities
//when idle, this will be called to search for what state to select next
//shoot ogres first because they will destroy walls
entity.findPersons();
if (entity.shouldAttackPersons()) {
entity.stateMachine.changeState(SHOOT_PERSONS);
}
//shoot if the opponent is aggressive or controls a lot of the map
else if (entity.isOpponentAggressive() ||
entity.shouldAttackBasedOnOpponentPercentage() ||
entity.shouldAttackBasedOnPercentOwned()) {
if (entity.shouldCastSpell(true)) {
entity.stateMachine.changeState(AGGRESIVE_SPELL);
} else {
entity.stateMachine.changeState(SHOOT);
}
//otherwise try to build
} else {
if (entity.shouldCastSpell(false)) {
entity.stateMachine.changeState(DEFENSIVE_SPELL);
} else {
entity.stateMachine.changeState(BUILD);
}
}
}
@Override
public void enter(AIPlayerConquer entity) {
}
@Override
public void exit(AIPlayerConquer entity) {
}
@Override
public boolean onMessage(AIPlayerConquer entity, Telegram telegram) {
return false;
}
}每个AI只有3个法术可用。下一个任务是将它们分成几个类别,以便应用一些通用逻辑来决定何时使用它们。首先,我创建了一个类型来描述一个咒语的目标:
public enum SpellTarget {
MY_CRYSTALS,
MY_WALLS,
MY_EMPTY_SPACE,
MY_CANNON,
OPPONENT_WALLS,
OPPONENT_EMPTY_SPACE,
OPPONENT_CANNON;
}然后,我在SpellType枚举中添加了一个新字段,并为每个法术类型设置了一个目标。接下来,我将法术分成攻击性和防御性法术,将其添加到SpellType枚举中。
public final static List<SpellType> aggresiveSpells = SpellType.getAggresiveSpells();
public final static List<SpellType> defensiveSpells = SpellType.getDefensiveSpells();
private static List<SpellType> getAggresiveSpells() {
List<SpellType> types = new ArrayList<SpellType>();
types.add(FIREBALL);
types.add(FIRE_WALL);
types.add(SKELETONS);
types.add(OGRES);
types.add(LIGHTNING);
types.add(CANNON_CHARGE);
types.add(ENERGIZE_CRYSTALS);
return types;
}
private static List<SpellType> getDefensiveSpells() {
List<SpellType> types = new ArrayList<SpellType>();
types.add(SHIELD);
types.add(STATIC_CHARGE);
types.add(DIG);
types.add(SHROUD);
types.add(BONUS_WALLS);
return types;
}当初始化AIPlayer类时,可用的咒语将被添加到本地列表中,以便他们知道自己有哪些咒语:
for (SpellType type : playerType.spells) {
if (SpellType.aggresiveSpells.contains(type)) {
this.aggresiveSpells.add(type);
} else if (SpellType.defensiveSpells.contains(type)) {
this.defensiveSpells.add(type);
}
}使用一些简单的代码来确定AI是否可以或不应该尝试施展一个咒语:
protected boolean shouldCastSpell(boolean offensive) {
if (this.timeSinceSpellCast < this.difficulty.minSecondsBetweenSpells) {
return false;
}
if (offensive) {
return this.canAffordAggressiveSpell();
} else {
return this.canAffordDefensiveSpell();
}
}
protected boolean canAffordAggressiveSpell() {
if (this.aggresiveSpells.size() == 0) {
return false;
}
this.spellsReadyToCast = this.getAffordableSpells(this.aggresiveSpells, this.spellsReadyToCast);
return this.spellsReadyToCast.size() > 0;
}
protected boolean canAffordDefensiveSpell() {
if (this.defensiveSpells.size() == 0) {
return false;
}
this.spellsReadyToCast = this.getAffordableSpells(this.defensiveSpells, this.spellsReadyToCast);
return this.spellsReadyToCast.size() > 0;
}
protected List<SpellType> getAffordableSpells(List<SpellType> availableSpells, List<SpellType> canAfford) {
canAfford.clear();
for (SpellType type : availableSpells) {
if (this.hasEnergyForSpell(type)) {
canAfford.add(type);
}
}
return canAfford;
}最后,当AI实际施放该咒语时,调用此代码:
public void tryToCastOffensiveSpell() {
this.spellsReadyToCast = this.getAffordableSpells(this.aggresiveSpells, this.spellsReadyToCast);
if (this.spellsReadyToCast.size() == 0) {
return;
}
this.tryToCastSpell(this.spellsReadyToCast);
}
public void tryToCastDefensiveSpell() {
this.spellsReadyToCast = this.getAffordableSpells(this.defensiveSpells, this.spellsReadyToCast);
if (this.spellsReadyToCast.size() == 0) {
return;
}
this.tryToCastSpell(this.spellsReadyToCast);
}
public void tryToCastSpell(List<SpellType> spells) {
this.timeSinceSpellCast = 0;
int randomIndex = this.random.nextInt(spells.size());
SpellType randomSpell = spells.get(randomIndex);
switch(randomSpell.target) {
case MY_CANNON:
this.castSpellOnMyCannon(randomSpell);
break;
case MY_CRYSTALS:
this.castSpellOnMyCrystals(randomSpell);
break;
case MY_EMPTY_SPACE:
this.castSpellOnMyEmptySpace(randomSpell);
break;
case MY_WALLS:
this.castSpellOnMyWalls(randomSpell);
break;
case OPPONENT_CANNON:
this.castSpellOnOpponentCannon(randomSpell);
break;
case OPPONENT_EMPTY_SPACE:
this.castSpellOnOpponentEmpty(randomSpell);
break;
case OPPONENT_WALLS:
this.castSpellOnOpponentWalls(randomSpell);
break;
}
}我认为这里的逻辑是相当清楚的,但我正在寻找其他可能不那么冗长的方法,并且像往常一样,我想了解代码可以改进的任何方法。谢谢!
另外,下面是一个AI法术在另一个上的截图:

发布于 2017-02-25 05:39:00
AIPlayerConquerState是非常不寻常的--枚举不是用来保持“业务”逻辑的,但它似乎是通过使用的API强制执行的。
一般情况:与其在公共API中的专门化(List,Set)相比,它更喜欢Collection;它为您选择/更改实现提供了更大的灵活性(请参见下面)。
SpellType:1) post 2)集合中缺少常量,如果定义为:
public final static Collection<SpellType> defensiveSpells = Collections.unmodifiableSet(
EnumSet.of( SHIELD, STATIC_CHARGE, DIG, SHROUD, BONUS_WALLS ) );
public final static Collection<SpellType> aggresiveSpells
= Collections.unmodifiableSet( EnumSet.complementOf(defensiveSpells) );然后,可以将法术集合初始化简化为(删除两个循环):
aggresiveSpells = EnumSet.copyOf( playerType.spells ).retainAll( SpellType.aggresiveSpells );
defensiveSpells = EnumSet.copyOf( playerType.spells ).retainAll( SpellType.defensiveSpells );“一些简单的代码”可以进一步简化(并加快):
protected boolean shouldCastSpell(boolean offensive) {
if (timeSinceSpellCast < difficulty.minSecondsBetweenSpells) {
return false;
}
return offensive ? hasAffordableSpells(aggresiveSpells) : hasAffordableSpells(defensiveSpells);
}
protected boolean hasAffordableSpells(Collection<SpellType> availableSpells) {
return availableSpells.stream().anyMatch( s -> hasEnergyForSpell(s) );
}最后一部分也可以简化:
public void tryToCastOffensiveSpell() {
List<SpellType> affordableSpells = aggresiveSpells.stream()
.filter( s -> hasEnergyForSpell(s) )
.collect( Collectors.toList() );
if (!affordableSpells.isEmpty()) { tryToCastSpell(affordableSpells); }
}祝你铸造法术好运:)
https://codereview.stackexchange.com/questions/156220
复制相似问题