首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >教一个古老的人工智能新魔法技能

教一个古老的人工智能新魔法技能
EN

Code Review用户
提问于 2017-02-24 17:15:16
回答 1查看 103关注 0票数 5

最近我想教我的城堡游戏的AI如何使用我添加到游戏中的咒语。你可以在这里试试这个游戏:铸铁件

一开始,我想我必须为四个可玩的种族中的每一个添加子类,以便说明每个种族都有不同的法术。然而,我最终想出了一种更通用的方法,它也同样有效。

我正在为人工智能使用gdx-ai库。最初(在咒语添加之前),AI的核心逻辑如下:

代码语言:javascript
复制
@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);
    }
}

只要做一些小小的改动,我就在核心逻辑中添加了咒语。我想把法术放在比常规动作更重要的位置,因为它们使游戏更令人兴奋。以下是全班同学:

代码语言:javascript
复制
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个法术可用。下一个任务是将它们分成几个类别,以便应用一些通用逻辑来决定何时使用它们。首先,我创建了一个类型来描述一个咒语的目标:

代码语言:javascript
复制
public enum SpellTarget {

    MY_CRYSTALS,
    MY_WALLS,
    MY_EMPTY_SPACE,
    MY_CANNON,
    OPPONENT_WALLS,
    OPPONENT_EMPTY_SPACE,
    OPPONENT_CANNON;

}

然后,我在SpellType枚举中添加了一个新字段,并为每个法术类型设置了一个目标。接下来,我将法术分成攻击性和防御性法术,将其添加到SpellType枚举中。

代码语言:javascript
复制
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类时,可用的咒语将被添加到本地列表中,以便他们知道自己有哪些咒语:

代码语言:javascript
复制
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是否可以或不应该尝试施展一个咒语:

代码语言:javascript
复制
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实际施放该咒语时,调用此代码:

代码语言:javascript
复制
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法术在另一个上的截图:

EN

回答 1

Code Review用户

回答已采纳

发布于 2017-02-25 05:39:00

AIPlayerConquerState是非常不寻常的--枚举不是用来保持“业务”逻辑的,但它似乎是通过使用的API强制执行的。

一般情况:与其在公共API中的专门化(List,Set)相比,它更喜欢Collection;它为您选择/更改实现提供了更大的灵活性(请参见下面)。

SpellType:1) post 2)集合中缺少常量,如果定义为:

代码语言:javascript
复制
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) );

然后,可以将法术集合初始化简化为(删除两个循环):

代码语言:javascript
复制
aggresiveSpells = EnumSet.copyOf( playerType.spells ).retainAll( SpellType.aggresiveSpells );
defensiveSpells = EnumSet.copyOf( playerType.spells ).retainAll( SpellType.defensiveSpells );

“一些简单的代码”可以进一步简化(并加快):

代码语言:javascript
复制
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) );
}

最后一部分也可以简化:

代码语言:javascript
复制
public void tryToCastOffensiveSpell() {
    List<SpellType> affordableSpells = aggresiveSpells.stream()
            .filter( s -> hasEnergyForSpell(s) )
            .collect( Collectors.toList() );
    if (!affordableSpells.isEmpty()) { tryToCastSpell(affordableSpells); }
}

祝你铸造法术好运:)

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/156220

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档