フェローシップの戦いKotH


45

この課題では、戦闘で他のすべてのフェローシップを打ち負かすことを目標とするフェローシップを作成します。

フェローシップ(チーム)は3人のキャラクターで構成されます。各キャラクターはチームの他のメンバーとは独立して動きますが、敵と戦うときは協力する必要があります。チームは回転しながらお互いに向かい合います。勝ちは3ポイント、同点は1ポイント、負けは0ポイントの価値があります。

キャラクターには能力があります。キャラクターが持っている能力の選択は、このKotHで最も重要な(そして楽しい)部分の1つです。それらはすべて強力で、敵を一掃する可能性があります。

キャラクターはヘルスポイント(HP)を持ち、HPが0になる(または下回る)と死亡します。対戦相手のチームのすべてのキャラクターが死亡した場合、あなたは勝ちます!

キャラクターにはマナがあります。ほとんどのアクションを実行するにはManaが必要であり、十分な数がない場合、そのアクションは使用できません。

キャラクターにはターン遅延があります。これにより、各ターン間のティック数が決まります(100から始まります)。低いほど良い。

文字には属性があります。各キャラクターの各属性には5のベースがあり、分割するために20の追加の属性ポイントが与えられます。属性ポイントを割り当てた後、プライマリ属性が最高の属性として設定されます。

利用可能な属性は次のとおりです。

  • 強さ:ターンごとに10 HPと0.5 HPを与える
  • 知性:ターンごとに7マナと7マナを与える
  • 敏ility性:ターン遅延を1減らす

移動、ビジョン、範囲の
範囲は次のとおりです(0を中心に)。一部の範囲はcardinalです。つまり、直接上下左右にしか移動できません。

    444
   43334
  4322234
 432111234
 432101234
 432111234
  4322234
   43334
    444

キャラクターの開始ビジョンは2です。同じフェローシップのプレイヤー間のビジョンは共有されます。

遊び方

建設
プレイヤーはフェローシップを構築します。 次の手順を実行する必要があります

  1. 各キャラクターにポイントを与えます。各キャラクターは各スタットで5から始まり、3の間でさらに20が分配されます。

  2. 各キャラクターに能力を与えます。各キャラクターは4つの能力スロットから始まり、能力はデフォルトで1スロットを取ります。一部の能力は反復可能で、キャラクターに複数回与えることができます。所有者の許可なしに別の提出の機能セットを使用することは許可されていません。

  3. ボット用のコード作成します。コードはJavaである必要があり、戦闘に使用されます(次のステップ)

行動

すべてのキャラクターは3つの標準アクションで始まります:

  1. ステップ:主な範囲1でキャラクターを移動する
  2. スライス:主属性範囲1 でPrimaryAttributeの敵を攻撃します
  3. 笑顔:何もしない

キャラクターのターンで、実行するアクションを選択する必要があります。アクションにはマナコストがあり、クールダウンがあります。これは、そのアクションを再度実行するまで待機する必要があるターンの数を定義します。

能力
各キャラクターには4つの能力スロットがあります。能力が斜体の場合、それはアクションです。

能力

名前説明マナクールダウン 
                                                     モビリティ                                                     

瞬き         正方形に移動範囲が4 2 2 
 スワップ          対象とスワップ位置5 
 テレポート      20 5の任意の場所に移動

ダッシュステップの範囲を1つ増やします。繰り返し可能                                               
Mobile Stepは8方向のいずれにも移動可能                                                  
                                                    攻撃                                                    

クイック         スライス2回3 0 
 ウィーブ         スライス表示されているすべての敵を1回15 10

各スライスを吸収すると、ターゲットのプライマリアトリビュートが1つ盗まれます。20ターン続く                    
各スライスを切断すると、隣接する敵に1/2のダメージを与えます                                           
クリティカルスライスが30%の確率で200%のダメージを与える。繰り返し可能                               
Feast Each SliceはHPを3増やします。                                             
8方向のいずれにも柔軟にスライス可能                                                      
マナを盗むスライスは2マナを盗みます。繰り返し可能                                                           
スライス時の再帰スライス0 3 
Rangedスライスの範囲に1を追加します                                                              
スワイプ同じターゲット上の連続する各スライスは、前回よりも3多いダメージを与えます               
                                                    ステータス                                                     

Dispel        ターゲットからすべてのステータスを削除します。範囲         2。20 10 
 デュエルは、あなたとターゲットのいずれかが死亡するまでフリーズします。範囲1 25 0 
 ノックアウト      あなたとターゲットは次の1000ティックでスタンします10 10 
 流星        すべての敵は次の100ティックでスタンし         ます25 10 
 リーシュターゲットは次の2ターンでフリーズします4 6 
         5ターンの間1 HPで敵を毒します5 0 
 沈黙      ターゲットは5ターンの間沈黙します5 7 
 遅い          ターゲットは次の3ターンの間40ティックだけ減速します10 5 
 スタン          ターゲットは次の300ティックの間スタンします10 10

コールド2の範囲内の他のすべてのキャラクターは10ティック遅くなります                                
免疫ステータスはあなたに適用できません                                                           
                                                    守備                                                    

  次の5つのダメージソースを強制的にブロックします。15 5累積しない 
 ゴースト         10 10すべてのダメージが回復、ターンを 
 癒す          3 HP 10 20のターゲットをヒール 
 の復元       すべてのユニットをバックフル健康へ20 40復元された 
 盾を        あなたの次のターン3 0までスライスすることはできません

スライスが25%の確率で回避されます。繰り返し可能                                         
Pillar Onlyは1ターンに1回しかスライスできません                                                            
復活殺されたとき、完全なHPで生き返る(ステータスなし)0 40 
スパイクダメージを与えたとき、ダメージの半分を返します                                           
                                                     ビジョン                                                      

クローク         チームは5ターンの間見えなくなります20 20 
 非表示          5ターンの間見えなくなります4 7 
 フェーズ         1ターンの間見えなくなります0 3 
 トラック         ターゲットは見えなくなり、10%のダメージを受けます。10ターン続く。5 5

Darkness Enemyの視界範囲は1減少しましたが、1を下回ることはできません。                                 
遠方視力範囲が2増加しました。                                                    
目に見えない敵の視界からあなたのターンを開始した場合、あなたは見えません                               
真の視界ターン開始時に範囲2内のすべての隠されたユニットを明らかにします                                     
                                                     ダメージ                                                      

ドレイン         ターゲットに5ダメージを与え、1範囲内にいる間に5 HP回復します10 5 
 ライトニング     敵に15ダメージを与えます20 10 
 K / O           ターゲットが20%HP未満の場合、ターゲットをキルします20 0 
 トラップ          目に見えないトラップを置きます。トラップは踏まれたときに15のダメージを与えます。スタック。10 2 
 ザップ           ターゲットに30ダメージを与える30 5

静的1範囲内のすべての敵に毎ターン5ダメージを与えます。繰り返し可能                       
                                                      統計                                                      

ワーウルフ      5ターンの間、すべてのステータスに10を加える30 25

バフHPプールを2倍にします。繰り返し可能                                                           
賢いアクションのクールダウンは20%短くなります。繰り返し可能                                             
集中マナの調整率がInt / 10増加します。繰り返し可能                                  
再生する再生率が強さ/ 2増加します。繰り返し可能                                 
スマートアクションのコストは2マナ少なくなります。繰り返し可能                                                      
強い10個の属性ポイントを獲得します。繰り返し可能                                                  
弱い15個の属性ポイントを失います。2つの能力スロットを獲得します(これにはそのうちの1つが必要です)                  
                                                      その他                                                      

ベア          缶は、それぞれのstat 8 10に5を持っている熊召喚 
 クローン         クローンを自分で。2つの能力スロットを取ります。100 100 
 盗む         このアクションを、最後に使用した敵ターゲットに置き換えます。         持続10ターン5 0 
 対象となる空の広場に範囲6 6 10 10 

ステータス:

  • スタンは、キャラクターがスマイルアクションのみを実行できるようにし、X ティック持続します
  • フリーズはキャラクターの移動を防ぎ、X回転します。
  • 沈黙は、キャラクターがスマイル、ステップ、またはスライス以外の何かを実行するのを防ぎ、Xターン続きます。
  • 毒はYターンの間、Xダメージでキャラクターにダメージを与えます。別の毒を適用すると、ダメージが加算され、持続時間が更新されます。
  • スローは、ターン間のティック数にXを追加します。それはあなたの次のターンには影響せず、後のターンのみです。
  • 目に見えないので、敵に見えたり損傷したりすることはありません。StepまたはSmile以外のアクションを実行すると、削除されます。相手があなたのビジョンを与える能力を持っている場合、不可視性は取り除かれます。

すべてのステータス(毒を除く)は、互いに独立して機能します。

サイドノート:

  • プライマリ属性に同点がある場合、STR> AGI> INTとして解決されます。
  • 10x10グリッドでプレイします。チームは反対側に配置されます。
  • 巧妙な場合を除き、パーセンテージは乗法的にスタックします。

提出ルール

次の2つの機能を実装する必要があります。

// Create *exactly* 3 Character templates.  You must return the same templates every time
public List<CharacterTemplate> createCharacters();

// Choose an action for a character.  If the action requires a target or location, it must be set.
public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character);

また、3つの変数(メンバー変数)にアクセスできます。

Set<ReadonlyCharacter> team;
Set<EnemyCharacter> enemies;
Map<Point2D, EnemyCharacter> visibleEnemies;

それでおしまい。以下に、完全なAPIをアルファベット順に見つけることができます。

class Ability and ReadonlyAbility
    int getNumSlots() returns the number of slots it takes up
    boolean repeatable() returns true if the ability can be repeated
    String name()
class Action and ReadonlyAction
    Set<Point2D> availableLocations()
    Set<ReadonlyCharacter> availableTargets()
    boolean basicAction() returns true if the action is Smile, Step, or Slice
    boolean breaksInvisibiliby()      
    int getCooldown() returns the cooldown cost (not the cooldown remaining)
    int getManaCost()
    String getName()
    int getRemainingCooldown()
    boolean isAvailable() returns true if the action can be performed
    boolean movementAction() returns true if the action is prevented when Frozen
    boolean needsLocation()
    boolean needsTarget()
    void setTarget(ReadonlyCharacter target)
    void setLocation(Point2D location)
class CharacterTemplate
    void addAbility(Ability)
    boolean canAddAbility(Ability)
    List<Ability> currentAbilities()
    Map<Stat, Integer> currentAttributes()
    int getRemainingPoints() returns the total number of ability points you have left to assign
    int getRemainingSlots() returns the total number of slots you have to assign
    int getStat(Stat stat)
    boolean isValid() returns true if your character template is complete and valid
class Point2D
    getX()
    getY()
class Range
    boolean isCardinal() returns true if the range only extends in the 4 cardinal directions
    int getRange() returns the distance of the range
class ReadonlyCharacter and EnemyCharacter
    Class characterClass()
    int cleverness()
    List<ReadonlyAbility> getAbilities()
    Point2D getLocation()   Not on EnemyCharacter
    double getHealth()
    double getMana()
    int getMaxHealth()
    int getMaxMana()
    Range getSightRange()
    Range getSliceRange()
    int getStat(Stat stat)
    Range getStepRange()
    ReadonlyAction getLastAction()
    boolean isFrozen()
    boolean isStunned()
    boolean isPoisoned()
    int getPoisonAmount()
    boolean isSilenced()
    boolean isInvisible()
    boolean isDead()
    Stat primaryStat()
    int smartness()
enum Stat
    INT, STR, AGI

上記は、提出に必要なすべての機能です。反射は許可されていません。 何らかの理由で送信が無効な場合は、削除するか、ヘッダーに「無効」を追加してください。 あなたの投稿はすべきではないパッケージ宣言を持っています。提出物は最初の複数行コードブロックに含まれている必要があり、最初の行にはファイル名が必要です。

プロジェクトの実行方法:

いくつかの方法があります。

  1. JARファイルをダウンロードして実行しjava -jar Fellowship.jarます。他の提出物をダウンロードする場合は、を渡し-q 99744ます。JREではなくJDKを指すjava 必要があります。
  2. gitリポジトリのクローンを作成して実行しgradle runます。gradleをインストールする必要があり、引数を渡したい場合は、-PappArgs="['arg1', 'args2']"
  3. クローンgitのレポ、そしてそれを自分でコンパイルします。あなたは以下のライブラリが必要になります:org.eclipse.collections:eclipse-collections-api:8.0.0org.eclipse.collections:eclipse-collections:8.0.0com.beust:jcommander:1.48com.google.code.gson:gson:2.7org.jsoup:jsoup:1.9.2

クローンを作成する場合は、--recursiveフラグを使用する必要があります。更新をプルする--recurse-submodules場合は、上記のいずれかの場合、クラスをsubmissions/javaフォルダーに入れる必要があります。gradleを使用している場合、または自分でコンパイルしている場合は、プロジェクト自体にクラスを配置できます。main関数のいくつかの行のコメントを解除し、クラスを指すように更新する必要があります。

スコアボード:

+------+-------------------+-------+
| Rank | Name              | Score |
+------+-------------------+-------+
|    1 | TheWalkingDead    | 738.0 |
|    2 | RogueSquad        | 686.0 |
|    3 | Spiky             | 641.0 |
|    4 | Invulnerables     | 609.0 |
|    5 | Noob              | 581.0 |
|    6 | Railbender        | 561.0 |
|    7 | Vampire           | 524.0 |
|    8 | LongSword         | 508.0 |
|    9 | SniperSquad       | 456.0 |
|   10 | BearCavalry       | 430.0 |
|   11 | StaticCloud       | 429.0 |
|   12 | PlayerWerewolf    | 388.0 |
|   13 | LongSwordv2       | 347.0 |
|   14 | Derailer          | 304.0 |
|   15 | Sorcerer          | 266.0 |
|   16 | CowardlySniperMk2 | 262.0 |
|   17 | TemplatePlayer    |  59.0 |
+------+-------------------+-------+

ご質問がある場合、または助けが必要な場合は、以下にコメントするか、チャットルームに参加しください! 頑張って楽しんでね


1
コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
デニス

稲妻はとしてリストされてDeal 15 damage to all enemiesいますが、目に見えない敵は稲妻の影響を受けません。これはバグですか?それ以外の場合、不可視性は私にはかなり強いようです
...-CommonGuy

1
この課題は、以前のこのような課題よりも非常に複雑です。長期にわたってこのようなものをより競争力のあるものにするフォーマットがここにあったことを願っています。
スパー

2
ええ、-gオプションを知っていますが、ボットを開発していたときに、まだ使用可能な状態ではなかったため、代替を作成し始めました。現時点では非常に初歩的ですが、視界の半径は見えます。これがベア騎兵対テンプレートプレイヤーの攻略です!キャプチャします。
ムージー

1
新しいキャラクターをテストしてスコアを更新できますか?
破壊可能なレモン

回答:


10

StaticCloud

近づいてくる人に静的なダメージを与える、知覚力を持つ成長する雲 次のもので構成されます。

  • 1/3 不可視部分
    • STR: 5; AGI: 5; INT: 25
    • クローン目に見えない静的
  • 2/3 可視部分
    • STR: 5; AGI: 5; INT: 25
    • クローン静的静的

ここに存在しないキャラクターを少なくとも1つ追加する限り、チームのここから1つのキャラクターを再利用できます。

StaticCloud.java
import java.util.Arrays;
import java.util.List;

import org.eclipse.collections.api.set.ImmutableSet;
import org.eclipse.collections.impl.factory.Sets;

import com.nmerrill.kothcomm.game.maps.Point2D;

import fellowship.abilities.ActionAbility;
import fellowship.abilities.damage.Static;
import fellowship.abilities.vision.Invisible;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.other.Clone;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;

public class StaticCloud extends SleafarPlayer {
    private CharacterTemplate invisibleTemplate() {
        return new CharacterTemplate(0, 0, 20,
                new ActionAbility(Clone::new), new Invisible(), new Static());
    }

    private CharacterTemplate visibleTemplate() {
        return new CharacterTemplate(0, 0, 20,
                new ActionAbility(Clone::new), new Static(), new Static());
    }

    @Override
    public List<CharacterTemplate> createCharacters() {
        return Arrays.asList(visibleTemplate(), invisibleTemplate(), visibleTemplate());
    }

    private class InvisibleCloud extends Character {
        protected InvisibleCloud(ReadonlyCharacter delegate) {
            super(delegate);
        }

        @Override
        protected ReadonlyAction choose() {
            ReadonlyAction clone = getAction(Clone.class);
            if (clone != null && (isVisible() || !isInEnemySightRange())) {
                int invisibleCount = countCharacters(InvisibleCloud.class);
                if (invisibleCount > 8 && setClosestSafeLocation(clone, getStaticLocations())) {
                    return clone;
                } else if (setCloneLocation(clone, invisibleCount < 3 ? 3 : 1)) {
                    return clone;
                }
            }
            if (step != null && isVisible() && isInEnemySliceRange() &&
                    setClosestSafeLocation(step, getStaticLocations())) {
                return step;
            }
            if (slice != null && isVisible() && setSliceTarget(slice, 0.01)) {
                return slice;
            }
            if (step != null) {
                ImmutableSet<Point2D> avoidLocations = !isVisible() || isInEnemySliceRange() ?
                        Sets.immutable.empty() : getEnemySliceLocations();
                if ((isVisible() || clone != null) && !getEnemyHiddenLocations().isEmpty() &&
                        setClosestLocation(step, avoidLocations, getEnemyHiddenLocations())) {
                    return step;
                }
                if (!getStaticLocations().contains(getLocation()) &&
                        setClosestLocation(step, avoidLocations, getStaticLocations())) {
                    return step;
                }
            }
            return smile;
        }
    }

    private class VisibleCloud extends Character {
        protected VisibleCloud(ReadonlyCharacter delegate) {
            super(delegate);
        }

        @Override
        protected ReadonlyAction choose() {
            ReadonlyAction clone = getAction(Clone.class);
            if (clone != null) {
                int visibleCount = countCharacters(VisibleCloud.class);
                if (visibleCount > 5 && setClosestSafeLocation(clone, getStaticLocations())) {
                    return clone;
                } else if (setCloneLocation(clone, visibleCount < 3 ? 2 : 1)) {
                    return clone;
                }
            }
            if (step != null && isInEnemySliceRange() && setClosestSafeLocation(step, getStaticLocations())) {
                return step;
            }
            if (slice != null && setSliceTarget(slice, 0.01)) {
                return slice;
            }
            if (step != null && !getStaticLocations().contains(getLocation())) {
                if (isInEnemySliceRange() ? setClosestLocation(step, getStaticLocations()) :
                        setClosestSafeLocation(step, getStaticLocations())) {
                    return step;
                }
            }
            return smile;
        }
    }

    @Override
    protected Character createCharacter(ReadonlyCharacter delegate) {
        if (hasAbility(delegate, Invisible.class)) {
            return new InvisibleCloud(delegate);
        } else {
            return new VisibleCloud(delegate);
        }
    }
}

3
Staticは不可視性を壊さないため、これは興味深いものです。
-Draco18s

7

テンプレートプレーヤー

用途は遠距離柔軟ザップ、およびKOを。必要に応じて、この機能セットを使用する権限があります。

このボットをテンプレートとして自由に使用して、独自のボットを作成してください。

最初の行でファイル名を変更し、独自の機能セットを選択する必要があることに注意してください。

TemplatePlayer.java
import com.nmerrill.kothcomm.game.maps.Point2D;
import fellowship.abilities.ActionAbility;
import fellowship.abilities.attacking.Flexible;
import fellowship.abilities.attacking.Ranged;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.damage.KO;
import fellowship.actions.damage.Zap;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;
import fellowship.Player;
import org.eclipse.collections.api.set.MutableSet;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class TemplatePlayer extends Player{
    private final double CRITICAL_HEALTH = 20;
    @Override
    public List<CharacterTemplate> createCharacters() {
        List<CharacterTemplate> templates = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            templates.add(new CharacterTemplate(10, 5, 5,
                    new Ranged(),
                    new Flexible(),
                    new ActionAbility(KO::new),
                    new ActionAbility(Zap::new)));
        }
        return templates;
    }

    @Override
    public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {
        int minPriority = Integer.MAX_VALUE;
        ReadonlyAction chosen = null;
        for (ReadonlyAction action: actions){
            int priority = getPriorityFor(action, character);
            if (priority < minPriority){
                chosen = action;
                minPriority = priority;
            }
        }
        if (chosen == null){
            throw new RuntimeException("No valid actions");
        }
        if (chosen.needsLocation()){
            chosen.setLocation(chooseLocationFor(chosen, character));
        } else if (chosen.needsTarget()){
            chosen.setTarget(chooseTargetFor(chosen));
        }
        return chosen;
    }

    private Point2D chooseLocationFor(ReadonlyAction action, ReadonlyCharacter character){
        if (action.movementAction()){
            if (character.getHealth() < CRITICAL_HEALTH){
                return fromEnemy(action.availableLocations());
            } else {
                return toEnemy(action.availableLocations());
            }
        }
        return toTeam(action.availableLocations());
    }

    private Point2D toEnemy(MutableSet<Point2D> availableLocations){
        if (visibleEnemies.isEmpty()){
            return availableLocations.iterator().next();
        }
        return availableLocations.minBy(p1 ->
                p1.cartesianDistance(visibleEnemies.keysView().minBy(p1::cartesianDistance))
        );
    }

    private Point2D fromEnemy(MutableSet<Point2D> availableLocations){
        if (visibleEnemies.isEmpty()){
            return availableLocations.iterator().next();
        }
        return availableLocations.maxBy(p1 ->
                p1.cartesianDistance(visibleEnemies.keysView().minBy(p1::cartesianDistance))
        );
    }

    private Point2D toTeam(MutableSet<Point2D> availableLocations){
        if (team.isEmpty()){
            return availableLocations.iterator().next();
        }
        return availableLocations.minBy(p1 ->
                p1.cartesianDistance(team.collect(ReadonlyCharacter::getLocation).minBy(p1::cartesianDistance))
        );
    }

    private ReadonlyCharacter chooseTargetFor(ReadonlyAction action){
        return action.availableTargets().minBy(ReadonlyCharacter::getHealth);
    }

    private int getPriorityFor(ReadonlyAction action, ReadonlyCharacter character){
        if (character.isInvisible() && action.breaksInvisibility()){
            return 1000;
        }
        if (action.getName().equals("Smile")){
            return 999;
        }
        if (action.movementAction()){
            if (character.getHealth() < 20){
                return 0;
            }
            return 998;
        }
        if (action.needsTarget()) {
            return ((int) action.availableTargets().minBy(ReadonlyCharacter::getHealth).getHealth());
        }
        return 997;
    }
}

7

CowardlySniperMk2

使用ザップ遠用 * 2、および非表示

このボットはco病者です。その最優先事項はターゲットにされません。そのために、彼は敵がどこにいるかを見るためにその優れた光景を使用しています。この知識を使用して、敵を追跡/追跡する際に、視認されることなく回避します。それが見られる場合、または次のターンで見られる場合、ボットは一時的に見えなくなります。

追跡モードでは、十分なマナとクールダウンのリセットがあり、最も弱い目に見える敵を「ザップ」します。

マナが10%に下がると、マナが回復するまで敵から遠ざかります。このようにして、追跡された敵をできるだけ速くザップできます。敵が持っているHPリジェネレーションを無効にすることを願っています。

「Zap」は無限の範囲であるため、チームメンバーはすべてザッピング時に同じボットをターゲットにします。

答えとして追加できるこの同じ基本的な考え方の他のバリエーションがあります。それらはすべて、存在する敵に応じて悪用/露出されるさまざまなメリット/デメリットを持っています。

CowardlySniperMk2.java

import com.nmerrill.kothcomm.game.maps.Point2D;
import fellowship.abilities.ActionAbility;
import fellowship.abilities.vision.FarSight;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.damage.Zap;
import fellowship.actions.vision.Hide;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import fellowship.*;

public class CowardlySniperMk2 extends Player{

    private final static boolean DEBUG=false; 
    private static Point2D lastAttackedEnemyLocation = null;
    private static HashMap<ReadonlyCharacter, Boolean> rechargingManaMap = new HashMap<>();
    private final double STANDARD_VISION_MOVEMENT_BUFFER = 3;
    private final double MIN_VISION_DISTANCE = 2;

    @Override
    public List<CharacterTemplate> createCharacters() {
        List<CharacterTemplate> templates = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            templates.add(new CharacterTemplate(8, 8, 4,
                    new ActionAbility(Zap::new),
                    new FarSight(),
                    new FarSight(),
                    new ActionAbility(Hide::new)));
        }
        return templates;
    }

    @Override
    public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {

        // get last flag for recharging mana
        Boolean rechargingMana = rechargingManaMap.get(character);
        if (rechargingMana == null || rechargingMana)
        {
            rechargingMana = !(character.getMana()>0.90*character.getMaxMana());
        }
        else
        {
            rechargingMana = character.getMana()<0.10*character.getMaxMana();
        }

        rechargingManaMap.put(character,rechargingMana);

        HashMap<Integer, ReadonlyAction> validActions = new HashMap<>();
        HashMap<Integer, String> actionString = new HashMap<>();

        // see if we have arrived at the last attack location of the enemy
        if (character.getLocation().equals(lastAttackedEnemyLocation))
        {
            lastAttackedEnemyLocation = null;
        }

        double closestEnemyVisionDistance = Double.MAX_VALUE;
        for ( Point2D enemyLocation : visibleEnemies.keySet())
        {
            final int enemyVisibiltyRange = visibleEnemies.get(enemyLocation).getSightRange().getRange();
            double visionDistanceDiff = character.getLocation().diagonalDistance(enemyLocation)-enemyVisibiltyRange;
            if (visionDistanceDiff< closestEnemyVisionDistance)
            {
                closestEnemyVisionDistance = visionDistanceDiff;
            }
        }

        for (ReadonlyAction action: actions){

            int priority=-1;
            String message = "";
            switch (action.getName())
            {
                case "Hide":
                    // are we, or will we be within sight range of an enemy
                    if (closestEnemyVisionDistance < STANDARD_VISION_MOVEMENT_BUFFER )
                    {
                        if (!character.isInvisible())
                        {
                            message = ""+closestEnemyVisionDistance;
                            priority = 1000;
                        }
                    }
                    break;

                case "Step":

                    Point2D chosenLocation = null;

                    // are we within sight range of an enemy or are we recharging mana?
                    if (closestEnemyVisionDistance < MIN_VISION_DISTANCE || rechargingMana)
                    {
                        message = "Fleeing (Seen) "+ closestEnemyVisionDistance;
                        priority = 800;

                        if (character.isInvisible())
                        {
                            message = "Fleeing (UnSeen) "+ closestEnemyVisionDistance;
                            priority = 500;
                        }

                        // simple enemy avoidance... chose location that is farthest away from closest enemy
                        double furthestDistance = 0;

                        for ( Point2D enemyLocation : visibleEnemies.keySet())
                        {
                            for (Point2D location : action.availableLocations())
                            {
                                if (location.diagonalDistance(enemyLocation) > furthestDistance)
                                {
                                    furthestDistance = location.diagonalDistance(enemyLocation);
                                    chosenLocation = location;
                                }
                            }
                        }

                        if (chosenLocation == null)
                        {
                            // no moves are better than staying in current location
                            priority = -1;
                            break;
                        }
                    }
                    // are we "tracking" an enemy?
                    else if (lastAttackedEnemyLocation !=null)
                    {
                        priority = 20;
                        message = "Tracking "+ closestEnemyVisionDistance;

                        // head toward last attacked enemy location
                        double distance = Integer.MAX_VALUE;
                        for (Point2D location : action.availableLocations())
                        {
                            if (location.diagonalDistance(lastAttackedEnemyLocation) < distance)
                            {
                                distance = location.diagonalDistance(lastAttackedEnemyLocation);
                                chosenLocation = location;
                            }
                        }
                    }
                    // are we outside the sight range of all enemies?
                    else if (closestEnemyVisionDistance > STANDARD_VISION_MOVEMENT_BUFFER)
                    {
                        // scout for an enemy

                        priority = 10;
                        message = "Scouting "+ closestEnemyVisionDistance;

                        // dumb random location selection... not optimal but is sufficent.
                        int index = getRandom().nextInt(action.availableLocations().size());
                        for (Point2D location : action.availableLocations())
                        {
                            chosenLocation= location;
                            if (--index == 0)
                            {
                                break;
                            }
                        }
                    }
                    else
                    {
                        // we are in the sweet zone... just out of enemy sight range but within our sight range
                        break;
                    }

                    action.setLocation(chosenLocation);
                    break;

                case "Zap":
                    message = ""+closestEnemyVisionDistance;
                    ReadonlyCharacter chosenTarget = null;
                    double chosenTargetHealth = Double.MAX_VALUE;

                    // target the weakest enemy
                    for (ReadonlyCharacter target : action.availableTargets())
                    {
                        if (target.getHealth() < chosenTargetHealth)
                        {
                            chosenTargetHealth = target.getHealth();
                            chosenTarget = target;
                        }
                    }

                    if (chosenTarget != null)
                    {
                        priority = 100;
                        action.setTarget(chosenTarget);
                        lastAttackedEnemyLocation = chosenTarget.getLocation();
                    }
                    else
                    {
                        // nothing to target
                    }

                    break;

                case "Smile":
                    priority = 0;
                    break;
            }

            // add the action to the collection of valid actions to perform
            if (priority >-1)
            {
                validActions.put(priority, action);
                actionString.put(priority, message);
            }

        }


        int highestPriority = -1;
        ReadonlyAction chosen = null;

        // choose the highest priority action
        for (Integer priority : validActions.keySet())
        {
            if (priority > highestPriority)
            {
                highestPriority = priority;
                chosen = validActions.get(priority);
            }
        }
        String message = actionString.get(highestPriority);

        if (chosen == null){
            throw new RuntimeException("No valid actions");
        }

        if (DEBUG) System.out.println(this+"("+System.identityHashCode(character)+"): "+chosen.getName()+ (rechargingMana?" Mana_charge":" Mana_usable")+" H: "+character.getHealth()+" M: "+character.getMana() +(character.isInvisible()?" InVis":" Vis") +" x: "+character.getLocation().getX()+" y: "+character.getLocation().getY()+" "+message);
        return chosen;
    }
}

ファイル名はCowardlySniperMk2である必要があります:)
ネイサンメリル

oops:Pカットアンドペーストは私を馬鹿のように見せます
ムージー

マリオカートWiiのB Dasher MK2を思い出させる:)
Kritixi Lithos

@KritixiLithos SplatoonのBamboozler 14 Mk IIを思い出させます。;)
TNT

7

ウォーキング・デッド

ゾンビ、誰もが知っています。彼らは、誰かが現れるまで何もしないグループにとどまります。彼らは殺すのが難しいです、そして、あなたがいくら殺しても、それは常にもっとあります。そして、彼らは通常あなたの背中の後ろのどこからでも現れます。

  • 1 x ゾンビ#1(最強、したがってアルファゾンビ)
    • STR: 25; AGI: 5; INT: 15
    • クローン復活強い
  • 2 x ゾンビ#2(クロージングクレジットでゾンビ#3になりたい人はいなかったため、両方が同じ数になりました)
    • STR: 15; AGI: 5; INT: 15
    • クローン復活吸収

ここに存在しないキャラクターを少なくとも1つ追加する限り、チームのここから1つのキャラクターを再利用できます。

TheWalkingDead.java
import java.util.Arrays;
import java.util.List;

import fellowship.abilities.ActionAbility;
import fellowship.abilities.attacking.Absorb;
import fellowship.abilities.defensive.Resurrect;
import fellowship.abilities.stats.Strong;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.other.Clone;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;

public class TheWalkingDead extends SleafarPlayer {
    private CharacterTemplate zombie1Template() {
        return new CharacterTemplate(20, 0, 10, new ActionAbility(Clone::new), new Resurrect(), new Strong());
    }

    private CharacterTemplate zombie2Template() {
        return new CharacterTemplate(10, 0, 10, new ActionAbility(Clone::new), new Resurrect(), new Absorb());
    }

    @Override
    public List<CharacterTemplate> createCharacters() {
        return Arrays.asList(zombie1Template(), zombie2Template(), zombie2Template());
    }

    private class Zombie extends Character {
        private int resurrectCountdown = 0;
        private double oldHealth;

        protected Zombie(ReadonlyCharacter delegate) {
            super(delegate);
            this.oldHealth = getHealth();
        }

        @Override
        protected ReadonlyAction choose() {
            if (getHealth() > oldHealth + getHealthRegen() + 0.1) {
                resurrectCountdown = 40;
            }
            if (resurrectCountdown > 0) {
                --resurrectCountdown;
            }
            oldHealth = getHealth();

            ReadonlyAction clone = getAction(Clone.class);
            if (resurrectCountdown > 0) {
                if (step != null && isInEnemySliceRange() && setAvoidEnemiesLocation(step)) {
                    return step;
                }
                if (clone != null && !getSliceLocations().isEmpty() && setClosestLocation(clone, getSliceLocations())) {
                    return clone;
                }
                if (clone != null && setCloneLocation(clone, 1)) {
                    return clone;
                }
                if (slice != null && setSliceTarget(slice, 0.01)) {
                    return slice;
                }
                if (step != null && setAvoidEnemiesLocation(step)) {
                    return step;
                }
            } else {
                if (clone != null && !getSliceLocations().isEmpty() && setClosestLocation(clone, getSliceLocations())) {
                    return clone;
                }
                if (clone != null && setCloneLocation(clone, 1)) {
                    return clone;
                }
                if (slice != null && setSliceTarget(slice, 0.01)) {
                    return slice;
                }
                if (step != null && !getSliceLocations().isEmpty() && setClosestLocation(step, getSliceLocations())) {
                    return step;
                }
            }
            return smile;
        }
    }

    @Override
    protected Character createCharacter(ReadonlyCharacter delegate) {
        return new Zombie(delegate);
    }
}

ボットを動作させることができません... Sleafarの基本クラスは、最新のマスターブランチにないように見えるfellowship.characters.CharacterInterfaceをインポートします...
Moogie


7

不死身

ほとんどすべてを生き残ることができる丈夫な戦士のチーム。これは多くのタイムアウトにつながりますが、残念ながら私はしばしば勝てません。しかし、私が見たマッチアップには勝てないものはなく、チームが負けた場合、キャラクターがまだ生き残っていることがよくあります。

このチームの最も難しいマッチアップはベア騎兵隊に対するものです(彼らは文字通りこのチームをワイプすることはできませんが、通常はその真の数字のためにタイブレークで勝ちます)。ローグ・スクワッド(チームは毒にやや弱い); そして、ヴァンパイア(なぜまだ完全にはわかりません)。

シミュレーションでは、チームはほとんど常に最初または2番目に来ます。そのスコアはかなり安定しています。勝者は通常、Rogue Squadが他の競合他社に対してどれだけうまくいくかに依存します(その配置はInvulnerablesの配置よりもはるかにランダムです)。

魔界の魔術師

  • STR:5; AGI:5; INT:25

MageはPillarForce Fieldのコンボを使用して自身を保護します。集束と高い知能を持つ、それがクールダウンするたびにキャストフォースフィールドに十分なマナを再生成します。対戦相手の敏Ag性が向上していないと仮定すると、5ターンごとに最大5つのスライスで攻撃することができ、その期間中に5つのダメージソースがブロックされます。言い換えれば、メイジは敗北するために呪文を絶対に必要とします。スライスは、あなたがどんなに上手くいってもそれ自体では機能しません。

メイジは緊急時にスライシングを介して25を攻撃できますが、ほとんどの場合ポイズンを繰り返しキャストします。ポイズンの範囲は無限であり、チームのビジョンによってのみ制限されるため、メイジはこのチームが非常にHPが多いまたは再生可能な敵を倒す方法です。チームの他のメンバーは彼らのビジョンを維持し、メイジは二次的なダメージを与えます。毒は必然的に最終的にHPの再生を追い越すことになり、その過程でスパイクなどの静的ダメージを心配する必要はありません。

巨大な崖のファルコナー

  • STR:35; AGI:5; INT:5

Falconerの主な仕事は、チームの残りのターゲットのビジョンを取得し、ターゲットを攻撃して、ハヤブサの目で地図を偵察することです。ファーサイトは十分な視界を提供するため、隠れたり逃げようとする敵は通常、マップの隅にある視界範囲に閉じ込められます。Falconerに再試行するのは簡単ではありません。True Sightは、目に見えない敵に対するこのチームの主要な手段です。大きく、したがって強いため、ファルコナーは損傷に対して非常に抵抗力があり、必要に応じて重いスライスができます。最後に、ファルコナーはその下の敵の群衆にファルコンを放ち、敵の中に織り込むよう命令し、大規模な(35)エリアダメージを与えます。

ファルコナーの仕事は回避的な敵を追い詰め、メイジが敵を中毒させ続けることができるように敵のビジョンを維持することに加えて、時々35を織る能力は敵の群れチームに対処するための鍵です。そのようにして多くの敵を攻撃し、残りのチームが終了するのに十分なだけ低くすることが可能です(理想的には別のウィーブで)。スウォーミングは、これらのルールの下では圧倒的な戦略であり、Weaveは数少ない本物のカウンターの1つです。それでも、それだけでは十分ではありません。

Trollbone Skullrender

  • STR:25; AGI:5; INT:5

Trollboneの仕事は、敵の大群を抑制し、他のユニットが仕事を行えるようにすることです。メイジのように、トロールボーンはノックアウトで無限の範囲の呪文を持っています。これはMage's Poisonと非常によくコンボします。敵と1つずつ(そして多くのチームに対して)直面することが可能になった場合、ファルコナーは視界を獲得し、トロールボーンはそれらを気絶させ、その後、メイジは彼らに毒を蓄積し、彼らは死んでしまいます何もすることができません(Knockoutはターゲット上で1000ティック持続し、Trollboneはそれより少し速くクールダウンを再生成します)。また、単一の強い敵からTrollboneを保護するのにも非常に優れています。意識がなければ彼は何もできません。もちろん、敵と頭蓋骨を粉砕すると、両方の脳震盪を残しがちであり、気絶して毒する(そして誰も気にしない他のステータスの束)。魔法に傾倒しているわけではない呪文を中心としたキャラクターとして、トロールボーンは知性ではなく敵の血を飲むことで魔法を再生し、ヒットごとにマナスチールを行います。これにより、MPの再生速度がかなり向上します(そして、st然とした敵はMPを簡単に盗むことができます)。最後に、Trollboneは時折大暴れに行くとします織りで頭を壊し、その血を飲みながら敵の出世の階段を。十分に大きい敵の群れに対して、これは実際にマナを取り戻し、ファルコナーが弱体化した群れを終わらせることができます(25 + 35は60なので、敵がある程度再生したとしても機能します)。

戦略

多くのチームとは異なり、私はチームビルディングだけでなくAIに多くの焦点を当てています。基本的なルールの1つは、チームが何か他のことをするのに忙しくなければ常にグループ化を試みることであり、彼らが囲まれてお互いを守ることが難しくなります。彼らが群がっている場合、彼らは隅に隠れようとします。一方、敵が逃げようとするか、逃げようとする場合、彼らはマップを歩き回り、ランダムなコーナーまたは中心を選び、パスします。これにより、Falconerが最終的にターゲットを見つけることがほぼ保証されます。この動きは、可能な限り敵が先制攻撃を行わないように設計されています。敵は自分自身でスライス範囲に足を踏み入れる必要があります。メイジは常にフォースフィールドのMPを残し、MPの消耗による損失を防ぎます(これが失敗する唯一の方法は、ダメージがなくてもフォースフィールドを通過できるAbsorbを使用することです)。通常、これは問題ではありません。通常、メイジは毎回問題なくポイズンをスパムできます。干渉されない場合、チームは敵を1つずつ追いかけ、視界に入ったときに気絶させ、死ぬまで繰り返し中毒します。他の敵と一緒に、チームは可能であればkitを試み、輪になって走り回り、ほとんどの敵を追いかけます。主な問題は群れにあり、これがここに多くのウィービングがある理由ですが、それでも実際に戦略を打ち負かすのは難しいようです。

Invulnerables.java
import com.nmerrill.kothcomm.game.maps.Point2D;
import fellowship.abilities.*;
import fellowship.abilities.attacking.*;
import fellowship.abilities.defensive.*;
import fellowship.abilities.vision.*;
import fellowship.abilities.stats.*;
import fellowship.abilities.statuses.*;
import fellowship.actions.*;
import fellowship.actions.attacking.*;
import fellowship.actions.damage.*;
import fellowship.actions.defensive.*;
import fellowship.actions.statuses.*;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;
import fellowship.characters.EnemyCharacter;
import fellowship.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class Invulnerables extends Player {
  @Override
  public List<CharacterTemplate> createCharacters() {
    List<CharacterTemplate> templates = new ArrayList<>();

    templates.add(new CharacterTemplate(0, 0, 20,
                                        new ActionAbility(Poison::new),
                                        new ActionAbility(ForceField::new),
                                        new Focused(),
                                        new Pillar()));

    templates.add(new CharacterTemplate(30, 0, 0,
                                        new ActionAbility(Weave::new),
                                        new Strong(),
                                        new FarSight(),
                                        new TrueSight()));

    templates.add(new CharacterTemplate(20, 0, 0,
                                        new ActionAbility(Weave::new),
                                        new ActionAbility(Knockout::new),
                                        new ManaSteal(),
                                        new Immune()));

    return templates;
  }

  private String lastIdentifier(String s) {
    String[] split = s.split("\\W");
    return split[split.length - 1];
  }

  private boolean hasAbility(ReadonlyCharacter character, String abilityName) {
    for (ReadonlyAbility ability : character.getAbilities()) {
      if (lastIdentifier(ability.name()).equals(abilityName))
        return true;
    }
    return false;
  }

  private boolean hasAbility(EnemyCharacter character, String abilityName) {
    for (ReadonlyAbility ability : character.getAbilities()) {
      if (lastIdentifier(ability.name()).equals(abilityName))
        return true;
    }
    return false;
  }

  private int getSquareDanger(ReadonlyCharacter character, Point2D square) {
    /* A square's danger is basically equal to the number of hits we'd
       expect to take when standing there. Each hit is worth 1; a hit of
       25 damage or more is worth 2. */
    int sliceDanger = 0;
    int otherDanger = 0;
    int cx = square.getX();
    int cy = square.getY();
    for (Point2D enemyLocation : visibleEnemies.keysView()) {
      EnemyCharacter enemy = visibleEnemies.get(enemyLocation);
      if (enemy.isStunned())
        continue; /* approaching stunned enemies is a good thing */
      int dx = enemyLocation.getX() - cx;
      int dy = enemyLocation.getY() - cy;
      if (dx < 0)
        dx = -dx;
      if (dy < 0)
        dy = -dy;
      if (dx + dy <= 1) {
        /* We're in Static range. */
        if (hasAbility(enemy, "Static"))
          otherDanger++;
      }
      if (dx + dy <= enemy.getSliceRange().getRange() &&
          (dx * dy == 0 || !enemy.getSliceRange().isCardinal())) {
        int sliceMultiplier = 1;
        if (hasAbility(enemy, "Quick") && !hasAbility(character, "Pillar"))
          sliceMultiplier *= 2;
        if (enemy.getStat(enemy.primaryStat()) >= 25)
          sliceMultiplier *= 2;
        if (hasAbility(character, "Pillar")) {
          if (sliceDanger >= sliceMultiplier)
            continue;
          sliceDanger = 0;
        }
        sliceDanger += sliceMultiplier;
      }
    }
    return sliceDanger + otherDanger;
  }

  private ReadonlyAction[] forceFieldAction = new ReadonlyAction[3];
  private int goalX = 5;
  private int goalY = 5;

  @Override
  public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {

    /* Which character are we? */
    int characterNumber;
    if (hasAbility(character, "Focused"))
      characterNumber = 0;
    else if (hasAbility(character, "Immune"))
      characterNumber = 1;
    else if (hasAbility(character, "TrueSight"))
      characterNumber = 2;
    else
      throw new RuntimeException("Unrecognised character!");

    /* If we're at the goal square, pick a new one. */
    if (goalX == character.getLocation().getX() &&
        goalY == character.getLocation().getY()) {
      int i = getRandom().nextInt(5);
      goalX = i < 2 ? 1 : i > 2 ? 9 : 5;
      goalY = i == 2 ? 5 : (i % 2) == 1 ? 1 : 9;
    }

    /* If there are a lot of visible enemies, try to group up in a corner in order
       to prevent being surrounded. */
    if (visibleEnemies.size() > 3) {
      int xVotes = 0;
      int yVotes = 0;
      for (ReadonlyCharacter ally : team) {
        xVotes += ally.getLocation().getX() >= 5 ? 1 : -1;
        yVotes += ally.getLocation().getY() >= 5 ? 1 : -1;
      }
      goalX = xVotes > 0 ? 9 : 0;
      goalY = yVotes > 0 ? 9 : 0;
    }

    /* We need to know our Force Field cooldowns even between turns, so store the
       actions in a private field for later use (they aren't visible via the API) */
    for (ReadonlyAction action : actions) {
      if (action.getName().equals("ForceField"))
        forceFieldAction[characterNumber] = action;
    }

    /* If we know Force Field, ensure we always hang on to enough mana to cast it, and
       never allow our mana to dip low enough that it wouldn't regenerate in time. */
    double mpFloor = 0.0;
    if (forceFieldAction[characterNumber] != null) {
      double mpRegen = character.getStat(Stat.INT) / 10.0;
      if (hasAbility(character, "Focused"))
        mpRegen *= 2;
      mpFloor = forceFieldAction[characterNumber].getManaCost();
      mpFloor -= forceFieldAction[characterNumber].getRemainingCooldown() * mpRegen;
    }
    if (mpFloor > character.getMana())
      mpFloor = character.getMana();

    /* We use a priority rule for actions. */
    int bestPriority = -2;
    ReadonlyAction bestAction = null;
    for (ReadonlyAction action : actions) {
      int priority = 0;
      if (lastIdentifier(action.getName()).equals("ForceField"))
        priority = 20; /* top priority */
      else if (character.getMana() - action.getManaCost() < mpFloor) {
        continue; /* never spend mana if it'd block a force field */
      } else if (lastIdentifier(action.getName()).equals("Quick") ||
                 lastIdentifier(action.getName()).equals("Slice")) {
        int damagePotential =
          lastIdentifier(action.getName()).equals("Quick") ? 50 : 25;
        /* We use these abilities with very high priority to /kill/ an enemy
           who's weak enough to die from the damage. If they wouldn't die,
           we're much more wary about attacking; we do it only if we have
           nothing better to do and it's safe. */
        ReadonlyCharacter chosenTarget = null;
        for (ReadonlyCharacter target : action.availableTargets()) {
          if (!isEnemy(target))
            continue;
          if (target.getHealth() <= damagePotential) {
            chosenTarget = target;
            priority = (damagePotential == 25 ? 19 : 18);
            break; /* can't do beter than this */
          }
          if (hasAbility(target, "Spikes") ||
              hasAbility(target, "Reflexive"))
            /*  (target.getLastAction() != null &&
                target.getLastAction().getName().equals("Ghost")) */
            continue; /* veto the target */
          priority = (damagePotential == 25 ? 3 : 4);
          chosenTarget = target;
        }
        if (chosenTarget == null)
          continue;
        action.setTarget(chosenTarget);
      } else if (lastIdentifier(action.getName()).equals("Weave")) {
        priority = visibleEnemies.size() >= 3 ? 14 :
          visibleEnemies.size() >= 1 ? 6 : -1;
      } else if (lastIdentifier(action.getName()).equals("Smile")) {
        /* If there's a stunned or poisoned enemy in view, we favour Smile
           as the idle action, rather than exploring, so that we don't
           move it out of view. Exception: if they're the only enemy;
           in that case, hunt them down. Another exception: if we're
           running into a corner. */
        for (EnemyCharacter enemy : visibleEnemies) {
          if (enemy.isStunned() || enemy.isPoisoned())
            if (visibleEnemies.size() > 1 && visibleEnemies.size() < 4)
              priority = 2;
        }
        /* otherwise we leave it as 0, and Smile only as a last resort */
      } else if (lastIdentifier(action.getName()).equals("Knockout")) {
        /* Use this only on targets who have more than 50 HP. It doesn't
           matter where they are: if we can see them now, knocking them
           out will guarantee we can continue to see them. Of course, if
           they're already knocked out, don't use it (although this case
           should never come up). If there's only one enemy target in
           view, knocking it out has slightly higher priority, because
           we don't need to fear enemy attacks if all the enemies are
           knocked out.

           Mildly favour stunning poisoned enemies; this reduces the
           chance that they'll run out of sight and reset the poison. */
        ReadonlyCharacter chosenTarget = null;
        for (ReadonlyCharacter target : action.availableTargets())
          if ((target.getHealth() > 50 || target.isPoisoned()) &&
              !target.isStunned() && isEnemy(target)) {
            chosenTarget = target;
            if (target.isPoisoned())
              break;
          }
        if (chosenTarget == null)
          continue;
        action.setTarget(chosenTarget);
        priority = visibleEnemies.size() == 1 ? 17 : 15;
      } else if (lastIdentifier(action.getName()).equals("Poison")) {
        /* Use this preferentially on stronger enemies, and preferentially
           on enemies who are more poisoned. We're willing to poison
           almost anyone, although weak enemies who aren't poisoned
           are faster to kill via slicing. The cutoff is at 49, not 50,
           so that in the case of evasive enemies who we can't hit any
           other way, we can wear them one at a time using poison. */
        ReadonlyCharacter chosenTarget = null;
        int chosenTargetPoisonLevel = -1;
        for (ReadonlyCharacter target : action.availableTargets()) {
          int poisonLevel = 0;

          if (!isEnemy(target))
            continue;
          if (target.isPoisoned())
            poisonLevel = target.getPoisonAmount() + 1;
          if (poisonLevel < chosenTargetPoisonLevel)
            continue;
          if (poisonLevel == 0 && target.getHealth() <= 49)
            continue; /* prefer stronger targets */
          if (poisonLevel == 0 && target.getHealth() == 50 &&
              chosenTarget != null)
            continue; /* we poison at 50, but not with other options */
          chosenTarget = target;
          chosenTargetPoisonLevel = poisonLevel;
          priority = 12;
        }
        if (chosenTarget == null)
          continue;
        action.setTarget(chosenTarget);
      } else if (action.movementAction()) {
        /* A move to a significantly safer square is worth 16.
           A move to a mildly safer square is worth 8.
           Otherwise, move to group, either with the enemy,
           the team, or the goal, at priority 1, if we
           safely can; that's our "idle" action. */
        int currentSquareDanger =
          getSquareDanger(character, character.getLocation());
        int bestSquareDanger = currentSquareDanger;
        int bestGroupiness = 0;
        Point2D bestLocation = null;
        priority = 1;
        for (Point2D location :
               action.availableLocations().toList().shuffleThis(getRandom())) {
          int danger = getSquareDanger(character, location);
          if (danger > bestSquareDanger)
            continue;
          else if (danger < bestSquareDanger) {
            priority = (currentSquareDanger - danger > 2)
              ? 16 : 8;
            bestSquareDanger = danger;
            bestLocation = location;
            bestGroupiness = 0; /* reset the tiebreak */
          }

          int cx = character.getLocation().getX();
          int xDelta = location.getX() - cx;
          int cy = character.getLocation().getY();
          int yDelta = location.getY() - cy;
          int groupiness = 0;
          /* Always hunt down a visible enemy when they're the only
             remaining enemy and doing so is safe. Otherwise, still
             favour hunting them down, but in that situation also
             consider factors like grouping and exploration. */
          for (Point2D enemyLocation : visibleEnemies.keysView())
            if (xDelta * (enemyLocation.getX() - cx) > 0 ||
                yDelta * (enemyLocation.getY() - cy) > 0)
              groupiness += (visibleEnemies.size() == 1 ? 99 : 5);
          /* If there are 4 or more visible enemies, then grouping is
             vitally important (so as to not get surrounded).
             Otherwise, it's more minor. */
          for (ReadonlyCharacter ally : team)
            if (xDelta * (ally.getLocation().getX() - cx) > 0 ||
                yDelta * (ally.getLocation().getY() - cy) > 0)
              groupiness += (visibleEnemies.size() > 3 ? 99 : 3);
          /* When exploring, we bias towards random map locations,
             changing location when we reach them. This helps us beat
             enemies that hide in the corners. When there are a lot
             of visible enemies, this changes to a bias to hide in a
             corner. */
          if (xDelta * (goalX - cx) > 0 ||
              yDelta * (goalY - cy) > 0)
            groupiness += (visibleEnemies.size() > 3 ? 99 : 4);
          if (groupiness >= bestGroupiness) {
            bestLocation = location;
            bestGroupiness = groupiness;
            /* leave priority, safety untouched */
          }
        }
        if (bestLocation == null)
          continue;
        action.setLocation(bestLocation);
      } else
        throw new RuntimeException("unknown action" + action.getName());

      if (priority > bestPriority) {
        bestPriority = priority;
        bestAction = action;
      }
    }
    if (bestAction == null)
      throw new RuntimeException("no action?");

    return bestAction;
  }
}

非常に効果的:)素晴らしいは効果的な戦略のようです!
ムージー

ForceFieldを使用するキャラクターは、ヘルスが50しかなくても、Noobsに負けることはありません!
Kritixi Lithos

@Sleafarが指摘したバグを修正しました。

私はあなたがあなたの鷹匠とtrollboneには、いくつかの非常に多くのskillpoints持っていると思う
Eumel

修正済み。説明のタイプミスであり、コードは正しかった。

7

RogueSquad

不正部隊の構成は次のとおりです。

  • スカウト 1人(マップの探索中は影にとどまる)
    • STR: 5; AGI: 5; INT: 25
    • クローン不可視遠方視力
  • 2 暗殺者(致命的な毒で敵を攻撃)
    • STR: 5; AGI: 5; INT: 25
    • クローン集中

両方が使用できるはるかに大きな力は、彼らをサポートするためにチームの追加メンバーを呼び出すことです。

ここに存在しないキャラクターを少なくとも1つ追加する限り、チームのここから1つのキャラクターを再利用できます。

RogueSquad.java
import java.util.Arrays;
import java.util.List;

import fellowship.abilities.ActionAbility;
import fellowship.abilities.stats.Focused;
import fellowship.abilities.vision.FarSight;
import fellowship.abilities.vision.Invisible;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.other.Clone;
import fellowship.actions.statuses.Poison;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;

public class RogueSquad extends SleafarPlayer {
    private CharacterTemplate scoutTemplate() {
        return new CharacterTemplate(0, 0, 20,
                new ActionAbility(Clone::new), new Invisible(), new FarSight());
    }

    private CharacterTemplate assasinTemplate() {
        return new CharacterTemplate(0, 0, 20,
                new ActionAbility(Clone::new), new ActionAbility(Poison::new), new Focused());
    }

    @Override
    public List<CharacterTemplate> createCharacters() {
        return Arrays.asList(assasinTemplate(), scoutTemplate(), assasinTemplate());
    }

    private class Scout extends Character {
        protected Scout(ReadonlyCharacter delegate) {
            super(delegate);
        }

        @Override
        protected ReadonlyAction choose() {
            ReadonlyAction clone = getAction(Clone.class);
            if (clone != null && (isVisible() || !isInEnemySightRange()) && setCloneLocation(clone, 3)) {
                return clone;
            }
            if (step != null && isVisible() && isInEnemySliceRange() && setAvoidEnemiesLocation(step)) {
                return step;
            }
            if (slice != null && isVisible() && setSliceTarget(slice, 0.01)) {
                return slice;
            }
            if (step != null && isVisible() && setAvoidEnemiesLocation(step)) {
                return step;
            }
            if (step != null && !isVisible() && setExploreLocation(step)) {
                return step;
            }
            return smile;
        }
    }

    private class Assasin extends Character {
        protected Assasin(ReadonlyCharacter delegate) {
            super(delegate);
        }

        @Override
        protected ReadonlyAction choose() {
            ReadonlyAction clone = getAction(Clone.class);
            ReadonlyAction poison = getAction(Poison.class);
            if (clone != null && setCloneLocation(clone, 1)) {
                return clone;
            }
            if (step != null && isInEnemySliceRange() && setAvoidEnemiesLocation(step)) {
                return step;
            }
            if (slice != null && setSliceTarget(slice, 0.01)) {
                return slice;
            }
            if (poison != null && setPoisonTarget(poison)) {
                return poison;
            }
            if (step != null && setAvoidEnemiesLocation(step)) {
                return step;
            }
            return smile;
        }
    }

    @Override
    protected Character createCharacter(ReadonlyCharacter delegate) {
        if (hasAbility(delegate, Invisible.class)) {
            return new Scout(delegate);
        } else if (hasAbility(delegate, Poison.class)) {
            return new Assasin(delegate);
        } else {
            throw new IllegalArgumentException();
        }
    }
}

すべてのボットの基本クラス

SleafarPlayer.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.collections.api.RichIterable;
import org.eclipse.collections.api.map.ImmutableMap;
import org.eclipse.collections.api.map.MutableMap;
import org.eclipse.collections.api.set.ImmutableSet;
import org.eclipse.collections.api.set.MutableSet;
import org.eclipse.collections.api.tuple.Pair;
import org.eclipse.collections.impl.factory.Maps;
import org.eclipse.collections.impl.factory.Sets;
import org.eclipse.collections.impl.list.primitive.IntInterval;
import org.eclipse.collections.impl.tuple.Tuples;

import com.nmerrill.kothcomm.game.maps.Point2D;

import fellowship.Player;
import fellowship.Range;
import fellowship.abilities.ReadonlyAbility;
import fellowship.abilities.attacking.Critical;
import fellowship.abilities.attacking.Reflexive;
import fellowship.abilities.defensive.Spikes;
import fellowship.abilities.statuses.Immune;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.attacking.Quick;
import fellowship.actions.attacking.Slice;
import fellowship.actions.mobility.Step;
import fellowship.actions.other.Smile;
import fellowship.characters.CharacterInterface;
import fellowship.characters.EnemyCharacter;
import fellowship.characters.ReadonlyCharacter;

public abstract class SleafarPlayer extends Player {
    private static final ImmutableSet<Point2D> MAP_LOCATIONS = IntInterval.fromTo(0, 9)
            .collect(x -> IntInterval.fromTo(0, 9).collect(y -> new Point2D(x, y))).flatCollect(t -> t).toSet()
            .toImmutable();
    protected static final Comparator<CharacterInterface> HEALTH_COMPARATOR = (o1, o2) ->
            Double.compare(o1.getHealth(), o2.getHealth());
    private static final Range BLOCKING_RANGE = new Range(1, true);
    private static final Range STATIC_RANGE = new Range(1);

    protected static boolean hasAbility(CharacterInterface character, Class<?> ability) {
        return character.getAbilities().anySatisfy(a -> a.abilityClass().equals(ability));
    }

    protected static boolean isBear(CharacterInterface character) {
        return character.getAbilities().isEmpty();
    }

    protected static double calcSliceDamage(CharacterInterface character) {
        return character.getStat(character.primaryStat()) * (hasAbility(character, Quick.class) ? 2.0 : 1.0);
    }

    protected static boolean setLocation(ReadonlyAction action, Point2D location) {
        if (location != null) {
            action.setLocation(location);
        }
        return location != null;
    }

    protected static boolean setTarget(ReadonlyAction action, ReadonlyCharacter target) {
        if (target != null) {
            action.setTarget(target);
        }
        return target != null;
    }

    protected abstract class Character {
        protected final ReadonlyCharacter delegate;

        protected Character(ReadonlyCharacter delegate) {
            super();
            this.delegate = delegate;
        }

        protected abstract ReadonlyAction choose();

        protected double getHealth() {
            return delegate.getHealth();
        }

        protected double getHealthRegen() {
            return delegate.getHealthRegen();
        }

        protected double getMana() {
            return delegate.getMana();
        }

        protected double getManaRegen() {
            return delegate.getManaRegen();
        }

        protected Point2D getLocation() {
            return delegate.getLocation();
        }

        protected boolean isVisible() {
            return !delegate.isInvisible();
        }

        protected double getSliceDamage() {
            return delegate.getStat(delegate.primaryStat());
        }

        protected boolean isInEnemySliceRange() {
            return getEnemySliceLocations().contains(delegate.getLocation());
        }

        protected boolean isInEnemySightRange() {
            return getEnemySightLocations().contains(delegate.getLocation());
        }

        protected boolean isInEnemyStepSightRange() {
            return getEnemyStepSightLocations().contains(delegate.getLocation());
        }

        protected double calcSliceRetaliationDamage(CharacterInterface character) {
            double result = 0.0;
            double ownDamage = getSliceDamage();
            for (ReadonlyAbility ability : character.getAbilities()) {
                if (ability.abilityClass().equals(Critical.class)) {
                    ownDamage = ownDamage * 2;
                }
            }
            for (ReadonlyAbility ability : character.getAbilities()) {
                if (ability.abilityClass().equals(Spikes.class)) {
                    result += ownDamage / 2.0;
                } else if (ability.abilityClass().equals(Reflexive.class)) {
                    result += character.getStat(character.primaryStat());
                }
            }
            return result;
        }

        protected double calcSpellRetaliationDamage(CharacterInterface character, double ownDamage) {
            double result = 0.0;
            for (ReadonlyAbility ability : character.getAbilities()) {
                if (ability.abilityClass().equals(Spikes.class)) {
                    result += ownDamage / 2.0;
                }
            }
            return result;
        }

        protected boolean setRandomLocation(ReadonlyAction action) {
            return setLocation(action, chooseRandom(action.availableLocations()));
        }

        protected boolean setRandomLocation(ReadonlyAction action, ImmutableSet<Point2D> avoidLocations) {
            return setLocation(action, chooseRandom(action.availableLocations().difference(avoidLocations)));
        }

        protected boolean setClosestLocation(ReadonlyAction action, ImmutableSet<Point2D> targetLocations) {
            return setLocation(action, chooseClosest(action.availableLocations(), targetLocations));
        }

        protected boolean setClosestLocation(ReadonlyAction action, ImmutableSet<Point2D> avoidLocations,
                ImmutableSet<Point2D> targetLocations) {
            return setLocation(action, chooseClosest(action.availableLocations().difference(avoidLocations),
                    targetLocations));
        }

        protected boolean setClosestHiddenLocation(ReadonlyAction action, ImmutableSet<Point2D> preferredLocations) {
            return setClosestLocation(action, getEnemySightLocations(), preferredLocations);
        }

        protected boolean setClosestSafeLocation(ReadonlyAction action, ImmutableSet<Point2D> preferredLocations) {
            return setClosestLocation(action, getEnemySliceLocations(), preferredLocations);
        }

        protected boolean setFarthestLocation(ReadonlyAction action, ImmutableSet<Point2D> targetLocations) {
            return setLocation(action, chooseFarthest(action.availableLocations(), targetLocations));
        }

        protected boolean setFarthestLocation(ReadonlyAction action, ImmutableSet<Point2D> avoidLocations,
                ImmutableSet<Point2D> targetLocations) {
            return setLocation(action, chooseFarthest(action.availableLocations().difference(avoidLocations),
                    targetLocations));
        }

        public boolean setCloneLocation(ReadonlyAction action, int distance) {
            ImmutableSet<Point2D> cloneLocations = distance < 2 ? team.collect(t -> t.getLocation()).toImmutable() :
                team.flatCollect(t -> t.rangeAround(new Range(distance))).difference(
                team.flatCollect(t -> t.rangeAround(new Range(distance - 1)))).toImmutable();
            if (cloneLocations.isEmpty()) {
                return setRandomLocation(action, getEnemySightLocations()) ||
                        setRandomLocation(action, getEnemySliceLocations()) ||
                        setRandomLocation(action);
            } else {
                return setClosestLocation(action, getEnemySightLocations(), cloneLocations) ||
                        setClosestLocation(action, getEnemySliceLocations(), cloneLocations) ||
                        setClosestLocation(action, cloneLocations);
            }
        }

        protected boolean setAvoidEnemiesLocation(ReadonlyAction action) {
            Point2D location = chooseFarthest(Sets.mutable.ofAll(action.availableLocations())
                    .with(delegate.getLocation()).difference(getEnemySliceLocations()), getEnemyLocations());
            if (location == null || location.equals(delegate.getLocation())) {
                return false;
            } else {
                return setLocation(action, location);
            }
        }

        protected boolean setBlockEnemiesLocation(ReadonlyAction action) {
            return setLocation(action, chooseRandom(action.availableLocations().intersect(getEnemyBlockingLocations())));
        }

        protected boolean setExploreLocation(ReadonlyAction action) {
            return visibleEnemies.size() < enemies.size() && getTeamHiddenLocations().notEmpty() &&
                    setClosestLocation(action, getEnemyStepSightLocations(), getTeamHiddenLocations());
        }

        protected boolean setSliceTarget(ReadonlyAction action, double minHealthReserve) {
            MutableSet<Pair<ReadonlyCharacter, Double>> pairs = action.availableTargets()
                    .collect(t -> Tuples.pair(t, calcSliceRetaliationDamage(t)));
            Pair<ReadonlyCharacter, Double> smallest = chooseSmallest(pairs, (o1, o2) -> {
                int c = Double.compare(o1.getTwo(), o2.getTwo());
                return c == 0 ? Double.compare(o1.getOne().getHealth(), o2.getOne().getHealth()) : c;
            });
            if (smallest == null || smallest.getTwo() > delegate.getHealth() - minHealthReserve) {
                return false;
            } else {
                return setTarget(action, smallest.getOne());
            }
        }

        protected boolean setPoisonTarget(ReadonlyAction action) {
            return setTarget(action, chooseSmallest(action.availableTargets().reject(c -> hasAbility(c, Immune.class)),
                    HEALTH_COMPARATOR));
        }

        protected final ImmutableSet<Point2D> getEnemyLocations() {
            if (enemyLocations == null) {
                enemyLocations = visibleEnemies.keysView().toSet().toImmutable();
            }
            return enemyLocations;
        }

        protected final ImmutableSet<Point2D> getEnemySliceLocations() {
            if (enemySliceLocations == null) {
                enemySliceLocations = visibleEnemies.keyValuesView()
                        .flatCollect(c -> c.getTwo().rangeAround(c.getTwo().getSliceRange(), c.getOne())).toSet()
                        .toImmutable();
            }
            return enemySliceLocations;
        }

        protected final ImmutableSet<Point2D> getEnemySightLocations() {
            if (enemySightLocations == null) {
                enemySightLocations = visibleEnemies.keyValuesView()
                        .flatCollect(c -> c.getTwo().rangeAround(c.getTwo().getSightRange(), c.getOne())).toSet()
                        .toImmutable();
            }
            return enemySightLocations;
        }

        protected final ImmutableSet<Point2D> getEnemyStepSightLocations() {
            if (enemyStepSightLocations == null) {
                enemyStepSightLocations = visibleEnemies.keyValuesView()
                        .flatCollect(c -> Sets.mutable.ofAll(c.getTwo().rangeAround(c.getTwo().getStepRange(), c.getOne()))
                                .with(c.getOne()).flatCollect(r -> c.getTwo().rangeAround(c.getTwo().getSightRange(), r)))
                        .toSet().toImmutable();
            }
            return enemyStepSightLocations;
        }

        protected final ImmutableSet<Point2D> getEnemyHiddenLocations() {
            if (enemyHiddenLocations == null) {
                enemyHiddenLocations = MAP_LOCATIONS.difference(getEnemySightLocations());
            }
            return enemyHiddenLocations;
        }

        protected final ImmutableSet<Point2D> getEnemyBlockingLocations() {
            if (enemyBlockingLocations == null) {
                enemyBlockingLocations = visibleEnemies.keyValuesView()
                        .flatCollect(c -> c.getTwo().rangeAround(BLOCKING_RANGE, c.getOne())).toSet().toImmutable();
            }
            return enemyBlockingLocations;
        }

        protected final ImmutableSet<Point2D> getTeamHiddenLocations() {
            if (teamHiddenLocations == null) {
                teamHiddenLocations = MAP_LOCATIONS.difference(team.flatCollect(c -> c.rangeAround(c.getSightRange())));
            }
            return teamHiddenLocations;
        }

        protected final ImmutableSet<Point2D> getTeamBlockingLocations() {
            if (teamBlockingLocations == null) {
                teamBlockingLocations = team.flatCollect(c -> c.rangeAround(BLOCKING_RANGE)).toImmutable();
            }
            return teamBlockingLocations;
        }

        protected final ImmutableSet<Point2D> getSliceLocations() {
            if (sliceLocations == null) {
                sliceLocations = visibleEnemies.keyValuesView()
                        .flatCollect(c -> c.getTwo().rangeAround(delegate.getSliceRange(), c.getOne())).toSet().toImmutable();
            }
            return sliceLocations;
        }

        protected final ImmutableSet<Point2D> getStaticLocations() {
            if (staticLocations == null) {
                staticLocations = visibleEnemies.keyValuesView()
                        .flatCollect(c -> c.getTwo().rangeAround(STATIC_RANGE, c.getOne())).toSet().toImmutable();
            }
            return staticLocations;
        }

        protected final ImmutableMap<Point2D, Double> getEnemySliceDamage() {
            if (enemySliceDamage == null) {
                MutableMap<Point2D, Double> tmp = MAP_LOCATIONS.toMap(l -> l, l -> 0.0);
                for (Pair<Point2D, EnemyCharacter> p : visibleEnemies.keyValuesView()) {
                    double damage = calcSliceDamage(p.getTwo());
                    for (Point2D l : p.getTwo().rangeAround(p.getTwo().getSliceRange(), p.getOne())) {
                        tmp.put(l, tmp.get(l) + damage);
                    }
                }
                enemySliceDamage = tmp.toImmutable();
            }
            return enemySliceDamage;
        }
    }

    protected ImmutableMap<ReadonlyCharacter, Character> characters = Maps.immutable.empty();

    private ImmutableMap<Class<?>, ReadonlyAction> actions = null;
    protected ReadonlyAction step = null;
    protected ReadonlyAction slice = null;
    protected ReadonlyAction smile = null;

    private ImmutableSet<Point2D> enemyLocations = null;
    private ImmutableSet<Point2D> enemySliceLocations = null;
    private ImmutableSet<Point2D> enemySightLocations = null;
    private ImmutableSet<Point2D> enemyStepSightLocations = null;
    private ImmutableSet<Point2D> enemyHiddenLocations = null;
    private ImmutableSet<Point2D> enemyBlockingLocations = null;
    private ImmutableSet<Point2D> teamHiddenLocations = null;
    private ImmutableSet<Point2D> teamBlockingLocations = null;
    private ImmutableSet<Point2D> sliceLocations = null;
    private ImmutableSet<Point2D> staticLocations = null;
    private ImmutableMap<Point2D, Double> enemySliceDamage = null;

    protected final <T> T chooseRandom(Collection<T> collection) {
        if (!collection.isEmpty()) {
            int i = getRandom().nextInt(collection.size());
            for (T t : collection) {
                if (i == 0) {
                    return t;
                }
                --i;
            }
        }
        return null;
    }

    protected final <T> T chooseSmallest(Collection<T> collection, Comparator<? super T> comparator) {
        if (!collection.isEmpty()) {
            List<T> list = new ArrayList<>();
            for (T t : collection) {
                if (list.isEmpty()) {
                    list.add(t);
                } else {
                    int c = comparator.compare(t, list.get(0));
                    if (c < 0) {
                        list.clear();
                    }
                    if (c <= 0) {
                        list.add(t);
                    }
                }
            }
            return list.get(getRandom().nextInt(list.size()));
        }
        return null;
    }

    protected final Point2D chooseClosest(Collection<Point2D> available, RichIterable<Point2D> targets) {
        if (targets.isEmpty()) {
            return chooseRandom(available);
        } else {
            Map<Point2D, Integer> map = new HashMap<>();
            for (Point2D a : available) {
                map.put(a, targets.collect(t -> t.cartesianDistance(a)).min());
            }
            return chooseSmallest(available, (o1, o2) -> Integer.compare(map.get(o1), map.get(o2)));
        }
    }

    protected final Point2D chooseFarthest(Collection<Point2D> available, RichIterable<Point2D> targets) {
        if (targets.isEmpty()) {
            return chooseRandom(available);
        } else {
            Map<Point2D, Integer> map = new HashMap<>();
            for (Point2D a : available) {
                map.put(a, targets.collect(t -> t.cartesianDistance(a)).min());
            }
            return chooseSmallest(available, (o1, o2) -> Integer.compare(map.get(o2), map.get(o1)));
        }
    }

    protected int countCharacters(Class<?> clazz) {
        return characters.count(c -> c.getClass().equals(clazz));
    }

    protected ReadonlyAction getAction(Class<?> clazz) {
        return actions.get(clazz);
    }

    protected abstract Character createCharacter(ReadonlyCharacter delegate);

    @Override
    public final ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {
        characters = team.collect(c -> characters.getIfAbsentWith(c, this::createCharacter, c))
                .groupByUniqueKey(c -> c.delegate).toImmutable();

        this.actions = Sets.immutable.ofAll(actions).groupByUniqueKey(ReadonlyAction::actionClass);
        step = getAction(Step.class);
        slice = getAction(Slice.class);
        smile = getAction(Smile.class);

        enemyLocations = null;
        enemySliceLocations = null;
        enemySightLocations = null;
        enemyStepSightLocations = null;
        enemyHiddenLocations = null;
        enemyBlockingLocations = null;
        teamHiddenLocations = null;
        teamBlockingLocations = null;
        sliceLocations = null;
        staticLocations = null;
        enemySliceDamage = null;

        return characters.get(character).choose();
    }
}

よくできました。タフなチームを打ち負かします...チャレンジが受け入れられました:P
Moogie

6

吸血鬼

私はこれに慣れていないので、自分が何をしているのかわかりませんが、面白いと思ったので、ここに私の試みがあります。

吸血鬼は敵を探し、最も弱いものをターゲットにし、彼らから命を奪い、強くなり、自分の健康を取り戻し、次の犠牲者に進む準備をします。重傷を負った場合は、自然の再生によって戦闘状態に回復するまで離れようとします。

使用して吸収し饗宴再生成強力ですべてとSTR

Vampire.java

import com.nmerrill.kothcomm.game.maps.Point2D;
import fellowship.abilities.ActionAbility;
import fellowship.abilities.attacking.Absorb;
import fellowship.abilities.attacking.Feast;
import fellowship.abilities.stats.Strong;
import fellowship.abilities.stats.Regenerate;
import fellowship.actions.ReadonlyAction;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;
import fellowship.Player;
import org.eclipse.collections.api.set.MutableSet;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class Vampire extends Player{
    private final double CRITICAL_HEALTH = 5;
    @Override
    public List<CharacterTemplate> createCharacters() {
        List<CharacterTemplate> templates = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            templates.add(new CharacterTemplate(30, 0, 0,
                    new Absorb(),
                    new Feast(),
                    new Regenerate(),
                    new Strong()));
        }
        return templates;
    }

    @Override
    public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {
        int minPriority = Integer.MAX_VALUE;
        ReadonlyAction chosen = null;
        for (ReadonlyAction action: actions){
            int priority = getPriorityFor(action, character);
            if (priority < minPriority){
                chosen = action;
                minPriority = priority;
            }
        }
        if (chosen == null){
            throw new RuntimeException("No valid actions");
        }
        if (chosen.needsLocation()){
            chosen.setLocation(chooseLocationFor(chosen, character));
        } else if (chosen.needsTarget()){
            chosen.setTarget(chooseTargetFor(chosen));
        }
        return chosen;
    }

    private Point2D chooseLocationFor(ReadonlyAction action, ReadonlyCharacter character){
        if (action.movementAction()){
            if (character.getHealth() <= CRITICAL_HEALTH){
                return fromEnemy(action.availableLocations());
            } else {
                return toEnemy(action.availableLocations());
            }
        }
        return toTeam(action.availableLocations());
    }

    private Point2D toEnemy(MutableSet<Point2D> availableLocations){
        if (visibleEnemies.isEmpty()){
            return availableLocations.iterator().next();
        }
        return availableLocations.minBy(p1 ->
                p1.cartesianDistance(visibleEnemies.keysView().minBy(p1::cartesianDistance))
        );
    }

    private Point2D fromEnemy(MutableSet<Point2D> availableLocations){
        if (visibleEnemies.isEmpty()){
            return availableLocations.iterator().next();
        }
        return availableLocations.maxBy(p1 ->
                p1.cartesianDistance(visibleEnemies.keysView().minBy(p1::cartesianDistance))
        );
    }

    private Point2D toTeam(MutableSet<Point2D> availableLocations){
        if (team.isEmpty()){
            return availableLocations.iterator().next();
        }
        return availableLocations.minBy(p1 ->
                p1.cartesianDistance(team.collect(ReadonlyCharacter::getLocation).minBy(p1::cartesianDistance))
        );
    }

    private ReadonlyCharacter chooseTargetFor(ReadonlyAction action){
        return action.availableTargets().minBy(ReadonlyCharacter::getHealth);
    }

    private int getPriorityFor(ReadonlyAction action, ReadonlyCharacter character){
        if (action.getName().equals("Smile")){
            return 1000;
        }
        if (action.movementAction()){
            if (character.getHealth() <= CRITICAL_HEALTH){
                return 0;
            }
            return 999;
        }
        if (action.needsTarget()) {
            return ((int) action.availableTargets().minBy(ReadonlyCharacter::getHealth).getHealth());
        }
        return 998;
    }
}

私はすべての受動的なアクションの使用が好きです!良いですね。
ムージー

6

クマ騎兵

AbsorbCloneBearを使用します。統計は(+9、+0、+11)です。

最初のターンで、全員が自分のクローンを作成し、フィールドに6人のキャラクターがいるようにします。それから彼らは敵に突撃し、いつでもクマをスパミングし、統計を吸収する攻撃で敵を弱めます。

コードは混乱していますが、うまくいくようです。Template Playerからその一部をコピーしました。

このチームのキャラクターは好きなように使用できます。

BearCavalry.java

import com.nmerrill.kothcomm.game.maps.Point2D;
import fellowship.abilities.ActionAbility;
import fellowship.abilities.attacking.Absorb;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.other.Clone;
import fellowship.actions.other.Bear;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;
import fellowship.Player;
import org.eclipse.collections.api.set.MutableSet;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class BearCavalry extends Player{
    @Override
    public List<CharacterTemplate> createCharacters() {
        List<CharacterTemplate> templates = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            templates.add(new CharacterTemplate(9, 0, 11,
                        new Absorb(),
                        new ActionAbility(Clone::new),
                        new ActionAbility(Bear::new)));
        }
        return templates;
    }

    @Override
    public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {
    for(ReadonlyAction action: actions){
        if (action.getName().equals("Clone") && action.isAvailable()){
        action.setLocation(toTeam(action.availableLocations(), character));
        return action;
        }
    }
    for(ReadonlyAction action: actions){
        if (action.getName().equals("Bear") && action.isAvailable()){
        action.setLocation(toEnemy(action.availableLocations(), character));
        return action;
        }
    }
    for(ReadonlyAction action: actions){
        if (action.getName().equals("Slice") && action.isAvailable()){
        action.setTarget(action.availableTargets().minBy(ReadonlyCharacter::getHealth));
        return action;
        }
    }
    for(ReadonlyAction action: actions){
        if (action.getName().equals("Step") && action.isAvailable()){
        action.setLocation(toEnemy(action.availableLocations(), character));
        return action;
        }
    }
    for(ReadonlyAction action: actions){
        if (action.getName().equals("Smile")){
        return action;
        }
    }
    return null;
    }

    private Point2D toTeam(MutableSet<Point2D> availableLocations, ReadonlyCharacter character){
        if (team.isEmpty()){
            return availableLocations.minBy(p1 ->
                        p1.diagonalDistance(character.getLocation())
                        );
        }
        return availableLocations.minBy(p1 ->
                    p1.diagonalDistance(team.collect(ReadonlyCharacter::getLocation).minBy(p1::cartesianDistance))
                    );
    }

    private Point2D toEnemy(MutableSet<Point2D> availableLocations, ReadonlyCharacter character){
        if (visibleEnemies.isEmpty()){
            return toTeam(availableLocations, character);
        }
        return availableLocations.minBy(p1 ->
                    p1.diagonalDistance(visibleEnemies.keyValuesView().minBy(p -> p.getTwo().getHealth()).getOne())
                    );
    }
}

あなたのクマは私のco病なスナイパーに対する効果的な戦略です:)ボットがザッピングを開始するためにスタンドオフ位置に入るにはあまりにも多くのターゲット!よくやった
Moogie

5

とがった

スパイキーは、彼の名前が示すように、盲目的に攻撃されることはありません。彼は戦車であり、HPを大量に再生することができ、トラックのようにヒットします。彼はマップの中央にホバーし、誰かが近づくのを待ちます。

使い方強い(STR +10)×2、再生成スパイクをフルSTR(40、0、0)に行きます。

Spiky.java

import fellowship.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;

import com.nmerrill.kothcomm.game.maps.Point2D;

import fellowship.abilities.defensive.Spikes;
import fellowship.abilities.stats.Regenerate;
import fellowship.abilities.stats.Strong;
import fellowship.actions.ReadonlyAction;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;

public class Spiky extends Player {

    @Override
    public List<CharacterTemplate> createCharacters() {
        List<CharacterTemplate> templates = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            templates.add(new CharacterTemplate(40, 0, 0,
                    new Strong(),
                    new Strong(),
                    new Regenerate(),
                    new Spikes()));
        }
        return templates;
    }

    @Override
    public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {

        ReadonlyAction chosen = null;
        Boolean canSlice = false;
        for (ReadonlyAction action: actions) {
            if (action.getName().equals("Slice")) {
                canSlice = true;
            }
        }

        for (ReadonlyAction action: actions) {
             if (action.getName().equals("Slice")) {
                 chosen = action;
                 chosen.setTarget(action.availableTargets().minBy(ReadonlyCharacter::getHealth));
             }
             if (!canSlice && action.getName().equals("Step")){
                 int x = ThreadLocalRandom.current().nextInt(3, 6 + 1);
                 int y = ThreadLocalRandom.current().nextInt(3, 6 + 1);
                 chosen = action;
                 Point2D destination = null;
                 if (visibleEnemies.isEmpty()){
                     destination = action.availableLocations().minBy(p1 -> p1.cartesianDistance(new Point2D(x, y)));
                 } else {
                     destination = action.availableLocations().minBy(p1 -> p1.cartesianDistance(visibleEnemies.keysView().minBy(p1::cartesianDistance)));
                 }
                 chosen.setLocation(destination);
             }
        }
        if (chosen == null){
            for (ReadonlyAction action: actions){
                if (action.getName().equals("Smile")){
                    chosen = action;
                }
            }
        }

        return chosen;
    }

}

>提出にはパッケージ宣言を含めないでください。提出物は最初の複数行コードブロックに含まれている必要があり、最初の行にはファイル名が必要です。
Kritixi Lithos

私はちょうどポストで言われたことを述べました。
クリティキシリトス

@KritixiLithosそれが私が正しかったことだと思います。ありがとう。
スラックス

素敵なボット!スパイキーは私のボットのベストを破った。
クリチキシリトス

あなたは変更することができますThreadLocalRandom.current()getRandom()?ゲームを決定論的にすることができます。
ネイサンメリル

5

魔術師

Weaveを使用して、すべての敵にできるだけ多くの即時ダメージを与えるために自分自身をクローンします(以前は稲妻でしたが、Weaveはより多くのダメージを与え、マナコストが低くなります。

Sorcerer.java

import com.nmerrill.kothcomm.game.maps.Point2D;
import fellowship.Player;
import fellowship.abilities.ActionAbility;
import fellowship.abilities.vision.TrueSight;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.attacking.Slice;
import fellowship.actions.attacking.Weave;
import fellowship.actions.mobility.Step;
import fellowship.actions.other.Clone;
import fellowship.actions.other.Smile;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.collections.api.set.MutableSet;

public class Sorcerer extends Player {

    @Override
    public List<CharacterTemplate> createCharacters() {
        List<CharacterTemplate> templates = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            templates.add(new CharacterTemplate(0, 0, 20,
                    new ActionAbility(Clone::new),
                    new TrueSight(),
                    new ActionAbility(Weave::new)));
        }
        return templates;
    }

    @Override
    public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {
        ReadonlyAction chosen = getBestAction(actions, character);
        if (chosen == null){
            throw new RuntimeException("No valid actions");
        }
        if (chosen.needsLocation()){
            chosen.setLocation(toEnemy(chosen.availableLocations()));
        } else if (chosen.needsTarget()){
            chosen.setTarget(chooseTargetFor(chosen));
        }
        return chosen;
    }

    private Point2D toEnemy(MutableSet<Point2D> availableLocations){
        if (visibleEnemies.isEmpty()){
            return availableLocations.minBy(p1 ->
                    p1.cartesianDistance(team.minBy(x -> p1.cartesianDistance(x.getLocation())).getLocation())
            );
        }

        return availableLocations.maxBy(p1 ->
                p1.cartesianDistance(visibleEnemies.keysView().maxBy(p1::cartesianDistance))
        );
    }

    private ReadonlyCharacter chooseTargetFor(ReadonlyAction action){
        return action.availableTargets().minBy(ReadonlyCharacter::getHealth);
    }

    private ReadonlyAction getBestAction(Set<ReadonlyAction> actions, ReadonlyCharacter character) {
        Map<Class<?>, ReadonlyAction> actionMap = new HashMap<>();
        for (ReadonlyAction action : actions) {
            actionMap.put(action.actionClass(), action);
        }

        ReadonlyAction clone = actionMap.get(Clone.class);
        if (clone != null && clone.isAvailable() && !clone.availableLocations().isEmpty()) {
            return clone;
        }

        ReadonlyAction weave = actionMap.get(Weave.class);
        if (weave != null && weave.isAvailable() && (clone == null || clone.getRemainingCooldown() > 0)) {
            return weave;
        }

        ReadonlyAction slice = actionMap.get(Slice.class);
        if (slice != null && slice.isAvailable() && !slice.availableLocations().isEmpty() && !character.isInvisible()) {
            return slice;
        }

        ReadonlyAction step = actionMap.get(Step.class);
        if (step != null && step.isAvailable()) {
            return step;
        }

        return actionMap.get(Smile.class);        
    }
}

クローニングはボットに人気のある選択肢のようです...私はあなたのボットをインスピレーションとして使うべきです。
ムージー

4

ロングソード

用途遠距離(スライスの範囲に追加1)、柔軟(8つの方向のいずれかに缶スライス)、クイック(スライス二回、マナ:3、クールダウン:0)、強い(あなたが10個の以上の属性ポイントを得ます)

統計

開始5ポイントがベースです

  • STR: 5 + 20 + 10
  • AGI: 5 + 0
  • INT: 5 + 0

まず、このボットの作成を本当に楽しんでいて、このKotHが本当に好きです(これはKotHチャレンジへの私の最初の投稿です!)。(ボットをもっと投稿するかもしれません)

ボット

このボットは、攻撃能力に依存してその敵を圧倒します。私がテストした限りでは、このボットは比較的低い健康状態のボットに対して本当に良いです。また、攻撃範囲が広く、視界内のほとんど(または半分)の敵を簡単に標的にすることができます。

このボットをNetHackの役割と比較するには、「LongSword」の概念と平均的なヘルスのために、Valkyrieによく似ていると思います。

名前

このボットの範囲は通常のボットよりもわずかに長く、あらゆる方向に攻撃できます。これはNetHackのLong Swordのほとんどを思い出させたので、ボットにそのような名前を付けました。

動作

キャラクターが敵キャラクターを見ることができない場合、敵キャラクターを見つけるためにフィールドの反対側(敵のスポーンエリア/敵の「ベース」)に移動します。敵を発見した場合、Quick、Slice(優先度を下げて)で攻撃します。敵をターゲットにできない場合、ボットは敵キャラクターに向かって破壊します。

キャラクターが敵キャラクター見ることができず、体力が低い場合、「ベース」/スポーンエリアに向かって後退します。

注:ボットは戦闘中に後退することはありません。このボットは決して笑わない。

regexr.comで次の正規表現を使用して、Javaコードをフォーマットされたコードブロックに変換しました。

以下のコードはコメント化されているため、理解しやすいはずです。それがどのように機能するかについて質問や説明があれば、バトルオブザフェローシップのチャットルームで私に連絡してください。

編集:ボットの開始位置に応じてボットの動き(順方向|逆方向)を調整するために、プログラムの小さな間違いを修正しました。これをするのを忘れていたので、今すぐ編集しました。

LongSword.java

import fellowship.*;
import com.nmerrill.kothcomm.game.maps.Point2D;
import fellowship.abilities.ActionAbility;
import fellowship.abilities.attacking.Flexible;
import fellowship.abilities.attacking.Ranged;
import fellowship.abilities.stats.Strong;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.attacking.Quick;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class LongSword/*Closest NetHack Role: Valkyrie*/ extends Player{

    //debugging
    private boolean debug = false;
    private void println(String text) {
        if(debug)
            System.out.println(text);
    }

    //variables use to hold the start Y coordinate of the bot
    private boolean started = false;
    private int startY = 5;

    @Override
    public List<CharacterTemplate> createCharacters() {
        List<CharacterTemplate> templates = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            templates.add(new CharacterTemplate(30, 0, 0,
                    new Ranged(), //Adds 1 to the range of Slice
                    new Flexible(), //Can Slice in any of the 8 directions
                    new ActionAbility(Quick::new), //Slice twice, Mana: 3, Cooldown: 0
                    new Strong())); //You gain 10 attribute points
        }
        return templates;
    }

    @Override
    public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {
        if(!started) {
            startY = character.getLocation().getY(); //giving startY the value of the bot's starting y-value
            started = true; //do this only once, that's why there is the if statement
        }

        ReadonlyAction current = null;

        //choosing action depending on priority
        int priority = Integer.MAX_VALUE;
        for(ReadonlyAction action:actions) {
            int priorityLocal = getPriority(action, character);
            if(priorityLocal < priority) {
                current = action;
                priority = priorityLocal;
            }
        }

        if (current == null){
            throw new RuntimeException("No valid actions");
        }

        println(current.getName());

        if(current.needsLocation()) {
            if(visibleEnemies.isEmpty()) {
                if (character.getHealth() < 100) {
                    //if has low health, go backwards towards "base"
                    //println("lowHealth");
                    current.setLocation(move(current, character, "backward"));
                } else {
                    //else go forwards to enemy's "base"
                    current.setLocation(move(current, character, "forward"));
                }
            }else{
                //go towards closest enemy
                current.setLocation(current.availableLocations().minBy(p1->p1.cartesianDistance(visibleEnemies.keysView().minBy(p1::cartesianDistance))));
            }
        }
        if(current.needsTarget()) {
            //get closest target
            current.setTarget(current.availableTargets().minBy(p1 -> 0));
        }

        return current;
    }

    //move backwards or forwards
    private Point2D move(ReadonlyAction readonlyAction, ReadonlyCharacter character, String direction) {
        Point2D location = null;

        //move direction depending on Y coordinate of point
        for(Point2D point2D:readonlyAction.availableLocations()) {
            switch (direction) {
                case "forward":
                    if(startY > 5) { //bot started at bottom
                        if (point2D.getY() < character.getLocation().getY())
                            location = point2D;
                    }else{ //bot started at top
                        if (point2D.getY() > character.getLocation().getY())
                            location = point2D;
                    }
                    break;
                case "backward":
                    if(startY > 5) { //bot started at bottom
                        if (point2D.getY() > character.getLocation().getY())
                            location = point2D;
                    }else{ //bot started at top
                        if (point2D.getY() < character.getLocation().getY())
                            location = point2D;
                    }
                    break;
            }

        }

        //if no available locations, just choose the first available location
        if(location == null) {
            location = readonlyAction.availableLocations().iterator().next();
        }

        println(location.getY()+","+character.getLocation().getY());

        return location;
    }

    private int getPriority(ReadonlyAction action, ReadonlyCharacter character) {
        if(visibleEnemies.isEmpty()) {
            //if there are no visible enemies, Step. In the choose function, this becomes move forward or backward depending on health
            if(action.getName().equals("Step")) {
                return 100;
            }
        }else {
            /*
             * PRIORITIES:
             *  1. Quick (Slice twice)
             *  2. Slice
             *  3. Step (when enemy is not in range --> move towards enemy)
             */
            if (action.getName().equals("Quick")) {
                return 1;
            }else if(action.getName().equals("Slice")) {
                return 10;
            }else if(action.getName().equals("Step")) {
                return 50;
            }
        }
        //Kids, don't Smile, instead Step or Slice
        return 1000;
    }
}

2
私のco病者を恥じさせる。良い仕事
-Moogie

4

脱線者

たくさんの論理エラーがあったので、2回削除しなければなりませんでした。:P

これは確かにあなたの計画を狂わせる可能性があります。;)

チーム:

  • クリティカルバフストロングクイックを備えた1人のキャラクター。敵を素早く倒し、倒すのは非常に困難です。+25 STR、+ 2 AGI、+ 3 INT
  • CleverCleverRestoreZapを含む 1文字。サポートとして後れを取らず、HPが不足しているチームメイトの健康を回復し、必要な自身を攻撃および防御できます。+14 STR、+ 3 AGI、+ 3 INT
  • TrueSightSpikesEvasive、およびWeaveを持つ1つのキャラクター。ヒットするのはそれほど簡単ではありません。あなたがヒットした場合、またはあなたが近づきすぎた場合、それはあなたを見てストライクします。+13 STR、+ 3 AGI、+ 4 INT
Derailer.java

import com.nmerrill.kothcomm.game.maps.Point2D;
import fellowship.Player;
import fellowship.abilities.ActionAbility;
import fellowship.abilities.ReadonlyAbility;
import fellowship.abilities.attacking.Critical;
import fellowship.abilities.defensive.Evasive;
import fellowship.abilities.defensive.Spikes;
import fellowship.abilities.stats.Buff;
import fellowship.abilities.stats.Clever;
import fellowship.abilities.stats.Strong;
import fellowship.abilities.vision.TrueSight;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.attacking.Quick;
import fellowship.actions.attacking.Weave;
import fellowship.actions.damage.Zap;
import fellowship.actions.defensive.Restore;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;

public class Derailer extends Player {
    private static final double CRITICAL_HEALTH_PCT = .175;

    @Override
    public List<CharacterTemplate> createCharacters() {
        List<CharacterTemplate> list = new ArrayList<>();

        list.add(new CharacterTemplate(14, 3, 3,
                                       new Clever(),
                                       new Clever(),
                                       new ActionAbility(Restore::new),
                                       new ActionAbility(Zap::new)));

        list.add(new CharacterTemplate(25, 2, 3,
                                       new Critical(),
                                       new Buff(),
                                       new ActionAbility(Quick::new),
                                       new Strong()));

        list.add(new CharacterTemplate(13, 3, 4,
                                       new TrueSight(),
                                       new Spikes(),
                                       new Evasive(),
                                       new ActionAbility(Weave::new)));
        return list;
    }

    @Override
    public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {
        List<ReadonlyAbility> abilities = character.getAbilities();
        ReadonlyAction action = null;

        for (ReadonlyAbility a : abilities) {
            String s = a.name();
            int i = s.lastIndexOf(".");
            if (i == -1)
                continue;
            s = s.substring(i+1, s.length());
            if (s.equals("Clever")) {
                action = getActionForChar1(character, actions);
                break;
            }
            else if (s.equals("Buff")) {
                action = getActionForChar2(character, actions);
                break;
            }
            else if (s.equals("Evasive")) {
                action = getActionForChar3(character, actions);
                break;
            }
        }

        return action;
    }

    private ReadonlyAction getActionForChar1(ReadonlyCharacter character, Set<ReadonlyAction> actions) {
        int members = (int) team.stream().filter(c -> !c.isDead()).count();

        List<ReadonlyAction> list = actions.stream()
                                           .sorted(Comparator.comparingInt(this::getPriority))
                                           .collect(Collectors.toList());

        for (ReadonlyAction a : list) {
            String name = a.getName();
            if (name.equals("Restore")) {
                for (ReadonlyCharacter teammate : team) {
                    if (teammate.getHealth() / teammate.getMaxHealth() < CRITICAL_HEALTH_PCT * (4 - members))
                        return a;
                }
            }
            else if (name.equals("Zap") && !a.availableTargets().isEmpty()) {
                a.setTarget(a.availableTargets()
                             .stream()
                             .reduce(
                                 BinaryOperator.minBy(
                                     Comparator.<ReadonlyCharacter>comparingDouble(e -> e.getHealth())
                                 )
                             )
                             .get()
                );
                return a;
            }
            else if (name.equals("Slice") && !a.availableTargets().isEmpty()) {
                a.setTarget(a.availableTargets().iterator().next());
                return a;
            }
            else if (name.equals("Smile"))
                return a;
        }
        throw new RuntimeException("No available actions");
    }

    private ReadonlyAction getActionForChar2(ReadonlyCharacter character, Set<ReadonlyAction> actions) {
        List<ReadonlyAction> list = actions.stream()
                                           .sorted(Comparator.comparingInt(this::getPriority))
                                           .collect(Collectors.toList());

        for (ReadonlyAction a : list) {
            String name = a.getName();
            if (name.equals("Quick") && !a.availableTargets().isEmpty()) {
                a.setTarget(a.availableTargets().minBy(ReadonlyCharacter::getHealth));
                return a;
            }
            else if (name.equals("Slice") && !a.availableTargets().isEmpty()) {
                a.setTarget(a.availableTargets().minBy(ReadonlyCharacter::getHealth));
                return a;
            }
            else if (name.equals("Step") && !a.availableLocations().isEmpty()) {
                Point2D e = getClosestEnemyPoint(character);
                if (e == null) {
                    Point2D p = character.getLocation();
                    if (p.getY() > 5) {
                        a.setLocation(a.availableLocations()
                                       .stream()
                                       .filter(x -> x.getY() < p.getY())
                                       .findFirst()
                                       .orElse(a.availableLocations().iterator().next()));
                    }
                    else if (p.getY() < 4) {
                        a.setLocation(a.availableLocations()
                                       .stream()
                                       .filter(x -> x.getY() > p.getY())
                                       .findFirst()
                                       .orElse(a.availableLocations().iterator().next()));
                    }
                    else
                        a.setLocation(randomLocation(new ArrayList<>(a.availableLocations())));
                }
                else {
                    int currentDistance = character.getLocation().cartesianDistance(e);
                    a.setLocation(a.availableLocations()
                                   .stream()
                                   .filter(x -> x.cartesianDistance(e) < currentDistance)
                                   .findFirst()
                                   .orElse(randomLocation(new ArrayList<>(a.availableLocations()))));
                }
                return a;
            }
            else if (name.equals("Smile"))
                return a;
        }
        throw new RuntimeException("No available actions");
    }

    private ReadonlyAction getActionForChar3(ReadonlyCharacter character, Set<ReadonlyAction> actions) {
        List<ReadonlyAction> list = actions.stream()
                                           .sorted(Comparator.comparingInt(this::getPriority))
                                           .collect(Collectors.toList());

        for (ReadonlyAction a : list) {
            String name = a.getName();
            if (name.equals("Weave") && visibleEnemies.keySet().size() > 1)
                return a;
            else if (name.equals("Slice") && !a.availableTargets().isEmpty()) {
                a.setTarget(a.availableTargets().iterator().next());
                return a;
            }
            else if (name.equals("Smile"))
                return a;
            else if (name.equals("Step")) {
                Point2D p = character.getLocation();
                if (!visibleEnemies.keySet().isEmpty()) {
                    Point2D e = getClosestEnemyPoint(character);
                    int currentDistance = character.getLocation().cartesianDistance(e);
                    a.setLocation(a.availableLocations()
                                   .stream()
                                   .filter(x -> x.cartesianDistance(e) < currentDistance)
                                   .findAny()
                                   .orElse(randomLocation(new ArrayList<>(a.availableLocations()))));
                }
                else if (p.getY() > 5) {
                    a.setLocation(a.availableLocations()
                                   .stream()
                                   .filter(x -> x.getY() < p.getY())
                                   .findFirst()
                                   .orElse(randomLocation(new ArrayList<>(a.availableLocations()))));
                }
                else if (p.getY() < 4) {
                    a.setLocation(a.availableLocations()
                                   .stream()
                                   .filter(x -> x.getY() > p.getY())
                                   .findFirst()
                                   .orElse(randomLocation(new ArrayList<>(a.availableLocations()))));
                }
                else
                    a.setLocation(randomLocation(new ArrayList<>(a.availableLocations())));
                return a;
            }
        }
        throw new RuntimeException("No available actions");
    }

    private Point2D getClosestEnemyPoint(ReadonlyCharacter c) {
        return visibleEnemies.keySet()
                             .stream()
                             .reduce(
                                 BinaryOperator.minBy(
                                     Comparator.comparingInt(x -> x.cartesianDistance(c.getLocation()))
                                 )
                             )
                             .orElse(null);
    }

    private int getPriority(ReadonlyAction action) {
        switch (action.getName()) {
            case "Quick":
            case "Restore":
            case "Weave":
                return 1;
            case "Zap": return 2;
            case "Slice": return 3;
            case "Step": return 4;
            case "Smile": return 5;
        }
        throw new IllegalArgumentException(String.valueOf(action));
    }

    private Point2D randomLocation(List<Point2D> l) {
        return l.get((int) (Math.random() * l.size()));
    }
}

興味深いチームダイナミクス!より高度なチームアクションを試してみたいと思うようになります:)
Moogie

ありがとう。チーム内に相乗効果を取り入れようとしましたが、3番目のキャラクターは少しずれているようです。おそらく、将来のボットでこの戦略を改善するでしょう。
TNT

興味深いことに、3番目の文字を最初の文字のコピーに置き換えると、はるかに良い結果が得られました。
TNT

4

狙撃兵

狙撃隊は以下で構成されています:

  • 1個のスポッター(利用可能な最高のスポッティングギアを装備しているため、マップ全体の概要を把握できます)
    • STR: 25; AGI: 5; INT: 5
    • ファーサイトファーサイトファーサイトファーサイト
  • 2 射手(最新のマルチターゲットスナイパーライフルを装備、唯一の欠点は発射速度が遅いことです)
    • STR: 25; AGI: 5; INT: 5
    • 織りクリティカルクリティカルクリティカル

ここに存在しないキャラクターを少なくとも1つ追加する限り、チームのここから1つのキャラクターを再利用できます。

SniperSquad.java
import java.util.Arrays;
import java.util.List;

import fellowship.abilities.ActionAbility;
import fellowship.abilities.attacking.Critical;
import fellowship.abilities.vision.FarSight;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.attacking.Weave;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;

public class SniperSquad extends SleafarPlayer {
    private static CharacterTemplate spotterTemplate() {
        return new CharacterTemplate(20, 0, 0,
                new FarSight(), new FarSight(), new FarSight(), new FarSight());
    }

    private static CharacterTemplate shooterTemplate() {
        return new CharacterTemplate(20, 0, 0,
                new ActionAbility(Weave::new), new Critical(), new Critical(), new Critical());
    }

    @Override
    public List<CharacterTemplate> createCharacters() {
        return Arrays.asList(shooterTemplate(), spotterTemplate(), shooterTemplate());
    }

    private class Spotter extends Character {
        protected Spotter(ReadonlyCharacter delegate) {
            super(delegate);
        }

        @Override
        protected ReadonlyAction choose() {
            if (slice != null && setSliceTarget(slice, 100.0)) {
                return slice;
            }
            if (step != null && isInEnemyStepSightRange() && setAvoidEnemiesLocation(step)) {
                return step;
            }
            if (slice != null && setSliceTarget(slice, 0.01)) {
                return slice;
            }
            if (step != null && setExploreLocation(step)) {
                return step;
            }
            return smile;
        }
    }

    private class Shooter extends Character {
        protected Shooter(ReadonlyCharacter delegate) {
            super(delegate);
        }

        @Override
        protected ReadonlyAction choose() {
            ReadonlyAction weave = getAction(Weave.class);
            if (weave != null && !visibleEnemies.isEmpty() &&
                    visibleEnemies.collectDouble(e -> calcSliceRetaliationDamage(e)).sum() < getHealth()) {
                return weave;
            }
            if (slice != null && setSliceTarget(slice, 100.0)) {
                return slice;
            }
            if (step != null && setAvoidEnemiesLocation(step)) {
                return step;
            }
            if (slice != null && setSliceTarget(slice, 0.01)) {
                return slice;
            }
            return smile;
        }
    }

    @Override
    protected Character createCharacter(ReadonlyCharacter delegate) {
        if (hasAbility(delegate, FarSight.class)) {
            return new Spotter(delegate);
        } else if (hasAbility(delegate, Weave.class)) {
            return new Shooter(delegate);
        } else {
            throw new IllegalArgumentException();
        }
    }
}

Hehe、あなたの狙撃兵は私の
co病

3

狼男

私はAI 選択の選択を書くのが得意ではありません。特にこれと同じくらい複雑なルールセットについては。ゲーム状態を表示し、意思決定を行うアクターを観察する低い能力と組み合わせて(および実行間で結果がわずかに異なるため、AIロジックを改善するためにわずかな変更の成功マージンを計算する能力はほとんどありません)、既存のボットセットを支配した優れた能力/属性選択。

用途は射撃スワイプを強力、およびそうでない場合と同じAI論理を使用してロングソードわずかに変更されているが、。

最も理想的な値を選択するのは困難です。変更を加えなくても、「最良」から「最悪」に低下することがあるためです。ここでは、ヘルスリトリートのしきい値は50ですが、10から70の間の値でも同様の結果が得られるようです(パフォーマンスの正確なピークを区別するのに十分なチャレンジを提供する他のボットはありません)。

PlayerWerewolf.java

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import com.nmerrill.kothcomm.game.maps.Point2D;

import fellowship.Player;
import fellowship.abilities.ActionAbility;
import fellowship.abilities.attacking.Ranged;
import fellowship.abilities.attacking.Swipe;
import fellowship.abilities.stats.Strong;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.stats.Werewolf;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;
import fellowship.characters.EnemyCharacter;

public class PlayerWerewolf extends Player {
    //variables use to hold the start Y coordinate of the bot
    private boolean started = false;
    private int startY = 5;

    @Override
    public List<CharacterTemplate> createCharacters() {
        List<CharacterTemplate> templates = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            templates.add(new CharacterTemplate(30, 0, 0,
                    new Ranged(), //Adds 1 to the range of Slice
                    new Swipe(), //Deal increasing damage
                    new ActionAbility(Werewolf::new), //Turn into a werewolf for 5 turns
                    new Strong())); //You gain 10 attribute points
        }
        return templates;
    }

    @Override
    public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {
        if(!started) {
            startY = character.getLocation().getY(); //giving startY the value of the bot's starting y-value
            started = true; //do this only once, that's why there is the if statement
        }

        ReadonlyAction current = null;

        //choosing action depending on priority
        int priority = Integer.MAX_VALUE;
        for(ReadonlyAction action:actions) {
            int priorityLocal = getPriority(action, character);
            if(priorityLocal < priority) {
                current = action;
                priority = priorityLocal;
            }
        }

        if (current == null){
            throw new RuntimeException("No valid actions");
        }

        if(current.needsLocation()) {
            if(visibleEnemies.isEmpty()) {
                if (character.getHealth() < 50) {
                    //if has low health, go backwards towards "base"
                    //println("lowHealth");
                    current.setLocation(move(current, character, "backward"));
                } else {
                    //else go forwards to enemy's "base"
                    current.setLocation(move(current, character, "forward"));
                }
            }else{
                //go towards closest enemy
                current.setLocation(current.availableLocations().minBy(p1->p1.cartesianDistance(visibleEnemies.keysView().minBy(p1::cartesianDistance))));
            }
        }
        if(current.needsTarget()) {
            //get closest target
            current.setTarget(current.availableTargets().minBy(p1 -> 0));
        }

        return current;
    }

    //move backwards or forwards
    private Point2D move(ReadonlyAction readonlyAction, ReadonlyCharacter character, String direction) {
        Point2D location = null;

        //move direction depending on Y coordinate of point
        for(Point2D point2D:readonlyAction.availableLocations()) {
            switch (direction) {
            case "forward":
                if(startY > 5) { //bot started at bottom
                    if (point2D.getY() < character.getLocation().getY())
                        location = point2D;
                }else{ //bot started at top
                    if (point2D.getY() > character.getLocation().getY())
                        location = point2D;
                }
                break;
            case "backward":
                if(startY > 5) { //bot started at bottom
                    if (point2D.getY() > character.getLocation().getY())
                        location = point2D;
                }else{ //bot started at top
                    if (point2D.getY() < character.getLocation().getY())
                        location = point2D;
                }
                break;
            }

        }

        //if no available locations, just choose the first available location
        if(location == null) {
            location = readonlyAction.availableLocations().iterator().next();
        }

        return location;
    }

    private int getPriority(ReadonlyAction action, ReadonlyCharacter character) {
        if(visibleEnemies.isEmpty()) {
            //if there are no visible enemies, Step. In the choose function, this becomes move forward or backward depending on health
            if(action.getName().equals("Step")) {
                return 100;
            }
        }else {
            /*
             * PRIORITIES:
             *  1. If near an enemy, and not a werewolf, turn into a werewolf
             *  2. Slice
             *  3. Step (when enemy is not in range --> move towards enemy)
             */
            if (action.getName().equals("Werewolf") && action.isAvailable()) {
                EnemyWrapper wrap = getNearestEnemy(character);
                //don't turn into a werewolf unless we're close to an enemy
                if(wrap.location.diagonalDistance(character.getLocation()) < 3) {
                    return 1;
                }
            }else if(action.getName().equals("Slice")) {
                return 10;
            }else if(action.getName().equals("Step")) {
                return 50;
            }
        }
        //Kids, don't Smile, instead Step or Slice
        return 1000;
    }

    private EnemyWrapper getNearestEnemy(ReadonlyCharacter character) {
        double closestEnemyDistance = Double.MAX_VALUE;
        Point2D closestEnemy = null;
        for ( Point2D enemyLocation : visibleEnemies.keySet()) {
            double visionDistanceDiff = character.getLocation().diagonalDistance(enemyLocation);
            if (visionDistanceDiff< closestEnemyDistance)
            {
                closestEnemyDistance = visionDistanceDiff;
                closestEnemy = enemyLocation;
            }
        }
        return new EnemyWrapper(visibleEnemies.get(closestEnemy), closestEnemy);
    }
    private static class EnemyWrapper {
        public final EnemyCharacter enemy;
        public final Point2D location;

        EnemyWrapper(EnemyCharacter e, Point2D l) {
            enemy = e;
            location = l;
        }
    }
}

いくつかの問題(パッケージ宣言、およびファイル名を最初の行に配置しないこと)があり、それらを修正しました。つまり、静的クラスを取得してコンパイラを通過させることはできません...
ネイサンメリル

私はそれを考え出した:あなたはインポートを逃していた:import fellowship.characters.EnemyCharacter;
ネイサン・メリル

@NathanMerrill私は、セカンダリクラスをEclipseの外部の内部クラスに結合しようとしましたが、おそらくそれがそうでした。
Draco18s

いいね!LongSwordの独自の移動機能を使用しました!
Kritixi Lithos

@KritixiLithosうん、物事の「ai」部分を書くのに苦労していたので、出発点を作るためだけに単純化したものを手に入れて、それは本当にうまくいった。私は彼らをいじって、もっと良くできるかどうかを確認しようとしました。なぜなら、相手が後ろにいても、相手が後ろにいるとしても、あまり前進していないので、彼らはただ前進し続けます差。主にオオカミもロングソードマンも不可視性に対抗する方法がないからです。
Draco18s

2

レイルベンダー

このボットは、3番目の文字が最初の文字に置き換えられたDerailerの単なるバージョンです。Derailerと比較して、はるかに優れた結果が得られます。

脱線者を作成している間、私はそれぞれのキャラクターに互いに相乗効果をもたらす能力を与えたかったのです。高いHPと攻撃力を持つ1つのキャラクターと、復元アクションを持つ別のキャラクターがうまく連携しました。ただし、3番目の文字がうまく収まるようには見えませんでした。これがおそらく、Derailerが良い結果を生まなかった主な理由です。だから私は、他の人とうまく働き、利益を得ることができる3番目のキャラクターを持つことはより良いアイデアだと思いました。

Railbender.java

import com.nmerrill.kothcomm.game.maps.Point2D;
import fellowship.Player;
import fellowship.abilities.ActionAbility;
import fellowship.abilities.ReadonlyAbility;
import fellowship.abilities.attacking.Critical;
import fellowship.abilities.stats.Buff;
import fellowship.abilities.stats.Clever;
import fellowship.abilities.stats.Strong;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.attacking.Quick;
import fellowship.actions.damage.Zap;
import fellowship.actions.defensive.Restore;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;

public class Railbender extends Player {
    private static final double CRITICAL_HEALTH_PCT = .175;

    @Override
    public List<CharacterTemplate> createCharacters() {
        List<CharacterTemplate> list = new ArrayList<>();

        list.add(new CharacterTemplate(14, 3, 3,
                                       new Clever(),
                                       new Clever(),
                                       new ActionAbility(Restore::new),
                                       new ActionAbility(Zap::new)));

        for (int k = 0; k < 2; k++) {
            list.add(new CharacterTemplate(25, 2, 3,
                                           new Critical(),
                                           new Buff(),
                                           new ActionAbility(Quick::new),
                                           new Strong()));
        }
        return list;
    }

    @Override
    public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {
        List<ReadonlyAbility> abilities = character.getAbilities();
        ReadonlyAction action = null;

        for (ReadonlyAbility a : abilities) {
            String s = a.name();
            int i = s.lastIndexOf(".");
            if (i == -1)
                continue;
            s = s.substring(i+1, s.length());
            if (s.equals("Clever")) {
                action = getActionForChar1(character, actions);
                break;
            }
            else if (s.equals("Buff")) {
                action = getActionForChar2(character, actions);
                break;
            }
        }

        return action;
    }

    private ReadonlyAction getActionForChar1(ReadonlyCharacter character, Set<ReadonlyAction> actions) {
        int members = (int) team.stream().filter(c -> !c.isDead()).count();

        List<ReadonlyAction> list = actions.stream()
                                           .sorted(Comparator.comparingInt(this::getPriority))
                                           .collect(Collectors.toList());

        Point2D closestEnemy = getClosestEnemyPoint(character);

        for (ReadonlyAction a : list) {
            String name = a.getName();
            if (name.equals("Restore")) {
                for (ReadonlyCharacter teammate : team) {
                    if (teammate.getHealth() / teammate.getMaxHealth() < CRITICAL_HEALTH_PCT * (4 - members))
                        return a;
                }
            }
            else if (name.equals("Zap") && !a.availableTargets().isEmpty() && closestEnemy != null &&
                     character.getLocation().cartesianDistance(closestEnemy) <= 4) {
                a.setTarget(a.availableTargets()
                             .stream()
                             .reduce(
                                 BinaryOperator.minBy(
                                     Comparator.<ReadonlyCharacter>comparingDouble(e -> e.getHealth())
                                 )
                             )
                             .get()
                );
                return a;
            }
            else if (name.equals("Slice") && !a.availableTargets().isEmpty()) {
                a.setTarget(a.availableTargets().iterator().next());
                return a;
            }
            else if (name.equals("Smile"))
                return a;
        }
        throw new RuntimeException("No available actions");
    }

    private ReadonlyAction getActionForChar2(ReadonlyCharacter character, Set<ReadonlyAction> actions) {
        List<ReadonlyAction> list = actions.stream()
                                           .sorted(Comparator.comparingInt(this::getPriority))
                                           .collect(Collectors.toList());

        for (ReadonlyAction a : list) {
            String name = a.getName();
            if (name.equals("Quick") && !a.availableTargets().isEmpty()) {
                a.setTarget(a.availableTargets().minBy(ReadonlyCharacter::getHealth));
                return a;
            }
            else if (name.equals("Slice") && !a.availableTargets().isEmpty()) {
                a.setTarget(a.availableTargets().minBy(ReadonlyCharacter::getHealth));
                return a;
            }
            else if (name.equals("Step") && !a.availableLocations().isEmpty()) {
                Point2D e = getClosestEnemyPoint(character);
                if (e == null) {
                    Point2D p = character.getLocation();
                    if (p.getY() > 5) {
                        a.setLocation(a.availableLocations()
                                       .stream()
                                       .filter(x -> x.getY() < p.getY())
                                       .findFirst()
                                       .orElse(a.availableLocations().iterator().next()));
                    }
                    else if (p.getY() < 4) {
                        a.setLocation(a.availableLocations()
                                       .stream()
                                       .filter(x -> x.getY() > p.getY())
                                       .findFirst()
                                       .orElse(a.availableLocations().iterator().next()));
                    }
                    else
                        a.setLocation(randomLocation(new ArrayList<>(a.availableLocations())));
                }
                else {
                    int currentDistance = character.getLocation().cartesianDistance(e);
                    a.setLocation(a.availableLocations()
                                   .stream()
                                   .filter(x -> x.cartesianDistance(e) < currentDistance)
                                   .findFirst()
                                   .orElse(randomLocation(new ArrayList<>(a.availableLocations()))));
                }
                return a;
            }
            else if (name.equals("Smile"))
                return a;
        }
        throw new RuntimeException("No available actions");
    }

    private Point2D getClosestEnemyPoint(ReadonlyCharacter c) {
        return visibleEnemies.keySet()
                             .stream()
                             .reduce(
                                 BinaryOperator.minBy(
                                     Comparator.comparingInt(x -> x.cartesianDistance(c.getLocation()))
                                 )
                             )
                             .orElse(null);
    }

    private int getPriority(ReadonlyAction action) {
        switch (action.getName()) {
            case "Quick":
            case "Restore":
                return 1;
            case "Zap": return 2;
            case "Slice": return 3;
            case "Step": return 4;
            case "Smile": return 5;
        }
        throw new IllegalArgumentException(String.valueOf(action));
    }

    private Point2D randomLocation(List<Point2D> l) {
        return l.get((int) (Math.random() * l.size()));
    }
}

すごい!これは、ディレイラより厳しい道である
KritixiのLithos

2

Noob/*Destroyer*/

使用強力 * 2、再生成、およびスタン(次300匹のダニ用スタンターゲット)

統計

  • STR:5 + 40
  • AGI:5 + 0
  • INT:5 + 0

AI

Noobのコードのほとんどは、私のLongSwordから取得されています。

戦略

キャラクターが最初に敵キャラクターを見ると、まず敵を気絶させ、次に敵が気絶している間に敵をスライスします。そして、その高い健康と再生により、NoobはStunを再び使えるようになるまで生き残ることができるはずです。

Noob.java
import fellowship.*;
import com.nmerrill.kothcomm.game.maps.Point2D;
import fellowship.Stat;
import fellowship.abilities.ActionAbility;
import fellowship.abilities.stats.Regenerate;
import fellowship.abilities.stats.Strong;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.defensive.Shield;
import fellowship.actions.statuses.Silence;
import fellowship.actions.statuses.Stun;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;
import fellowship.Player;
import org.eclipse.collections.api.set.MutableSet;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class Noob/*Destroyer*/ extends Player {

    private boolean debug = false;
    private void println(String text) {
        if(debug)
            System.out.println(text);
    }

    private boolean started = false;
    private int startY = 5;

    @Override
    public List<CharacterTemplate> createCharacters() {
        List<CharacterTemplate> templates = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            templates.add(new CharacterTemplate(40, 0, 0,
                    new Regenerate(),
                    new ActionAbility(Stun::new),
                    new Strong(),
                    new Strong()));
        }
        return templates;
    }

    @Override
    public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {
        if(!started) {
            startY = character.getLocation().getY();
            started = true;
        }

        ReadonlyAction readonlyAction = null;

        //get priority of action
        int priority = Integer.MAX_VALUE;

        for(ReadonlyAction action:actions) {
            int priorityLocal = getPriority(action, character);
            if(priorityLocal < priority) {
                readonlyAction = action;
                priority = priorityLocal;
            }
        }

        if (readonlyAction == null){
            println("NULL!");
            throw new RuntimeException("No valid actions");
        }

        //movement
        if(readonlyAction.needsLocation()) {
            if(visibleEnemies.isEmpty()) {
                if (character.getHealth() < 100) {
                    readonlyAction.setLocation(move(readonlyAction, character, "backward"));
                } else {
                    readonlyAction.setLocation(move(readonlyAction, character, "forward")); //enemy base is "forward"
                }
            }else{
                readonlyAction.setLocation(readonlyAction.availableLocations().minBy(p1->p1.cartesianDistance(visibleEnemies.keysView().minBy(p1::cartesianDistance))));
            }
        }

        if(readonlyAction.needsTarget()) {
            readonlyAction.setTarget(readonlyAction.availableTargets().minBy(p1 -> 0));
        }

        return readonlyAction;
    }

    private Point2D move(ReadonlyAction readonlyAction, ReadonlyCharacter character, String direction) {
        Point2D location = null;

        for(Point2D point2D:readonlyAction.availableLocations()) {
            switch (direction) {
                case "forward":
                    if(startY > 5) { //bot starts at bottom
                        if (point2D.getY() < character.getLocation().getY())
                            location = point2D;
                    }else{ //bot starts at top
                        if (point2D.getY() > character.getLocation().getY())
                            location = point2D;
                    }
                    break;
                case "backward":
                    if(startY > 5) { //bot starts at bottom
                        if (point2D.getY() > character.getLocation().getY())
                            location = point2D;
                    }else{ //bot starts at top
                        if (point2D.getY() < character.getLocation().getY())
                            location = point2D;
                    }
                    break;
            }

        }

        if(location == null) {
            location = readonlyAction.availableLocations().iterator().next();
        }
        return location;
    }

    private int getPriority(ReadonlyAction action, ReadonlyCharacter character) {
        if(visibleEnemies.isEmpty()) {
            if(action.getName().equals("Step")) {
                return 100;
            }
        }else {
            if (action.getName().equals("Slice")) {
                return 10;
            }else if(action.getName().equals("Step")) {
                return 50;
            }else if(action.getName().equals("Stun") && !action.availableTargets().minBy(p1->0).isStunned()) {
                //if target is not stunned, stun 'em
                return 1;
            }
        }
        return 1000;
    }
}

2

リビングウォール

戦場に沿って歩くことができる生きた木の壁で、近づいてくる敵に強い命中を与え、それらから樹液を排出して最大の健康を強化します。そのルートシステムは、振動を検出することができ、目に見えない敵でさえも打ち出すことができます。次のもので構成されます。

  • 2 ブランチSTR 35、AGI 5、INT 5、StrongBuffBuffAbsorb
  • 1 ルートSTR 25、AGI、5、INT、5、True SightBuffBuffAbsorb

AIは非常にシンプルです。チームに最も近い敵を見つけ、壁全体がその1人の敵に焦点を合わせます。わずかな合併症のみがあります:敵が見えない場合は、マップのランダムなコーナーや中心に向かって歩きます(したがって、最終的に隠れている敵を追い詰めます)。敵が手の届く範囲にいる場合は、ターゲットの敵ではない場合でも攻撃します(ただし、ターゲットの敵に集中することを好みます。

チームは非常にうまく機能しています。シミュレーションでは、それを打ち負かすことができる唯一のチーム(執筆時点で存在する)はRogueSquadであり、それでも常にではありません(RogueSquadが壁の力で死ぬこともあります)。Invulnerablesは、ドローをこすり落とすことがあります。

チームの成功の基本的な理由は、Buff×2とAbsorbの組み合わせによるものです。これは、STRの主敵を攻撃するたびに、短期的には40 HPを効果的に獲得することを意味します(盗まれたSTRからの再生が増加するため、長期的には10 HPになりますが、それまでに戦闘は終了します自然再生は私たちを乗り切るはずです)、さらにその上に12.5または17.5の自然再生率を考えると、再生に追いつくのに十分な速さでダメージを与えることは基本的に不可能です(AGIチームは潜在的に戦術を実行しますが、まだ誰もそれらを構築していません)。{ 更新:どうやらこのコンボは実際には動作しません(Absorbは10 HPしか消費しません)が、とにかくチームが勝ちます。}一方、敵がそうでなければSTR-primary、彼らは繰り返し25または35のダメージを与えることを好まないでしょう(実際、彼らの1つのターン内で集中する可能性が非常に高いかもしれません)。敵がINTプライマリであり、自分自身を守るために呪文を使用している場合(こんにちはInvulnerables!)、Absorbは最終的に、呪文を唱えることができないポイントまでMPを使い果たします。(さらに、ほとんどの呪文から基本的に何も恐れることはありません。クールダウンは長すぎてダメージが私たちの再生を上回ることができません。主な例外は、誰もまだ走っていないTrapと、1000または1400 HP、ただし、壁が最初にキャスターを倒さなければ機能します。)True Sightは、目に見えない敵を実際に倒すことができる唯一の能力です。

LivingWall.java
import com.nmerrill.kothcomm.game.maps.Point2D;
import fellowship.abilities.*;
import fellowship.abilities.attacking.*;
import fellowship.abilities.defensive.*;
import fellowship.abilities.vision.*;
import fellowship.abilities.stats.*;
import fellowship.abilities.statuses.*;
import fellowship.actions.*;
import fellowship.actions.attacking.*;
import fellowship.actions.damage.*;
import fellowship.actions.defensive.*;
import fellowship.actions.statuses.*;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;
import fellowship.characters.EnemyCharacter;
import fellowship.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class LivingWall extends Player {
  @Override
  public List<CharacterTemplate> createCharacters() {
    List<CharacterTemplate> templates = new ArrayList<>();

    for (int i = 0; i < 2; i++)
      templates.add(new CharacterTemplate(30, 0, 0,
                                          new Absorb(),
                                          new Strong(),
                                          new Buff(),
                                          new Buff()));
    templates.add(new CharacterTemplate(20, 0, 0,
                                        new Absorb(),
                                        new TrueSight(),
                                        new Buff(),
                                        new Buff()));

    return templates;
  }

  private String lastIdentifier(String s) {
    String[] split = s.split("\\W");
    return split[split.length - 1];
  }

  private boolean hasAbility(ReadonlyCharacter character, String abilityName) {
    for (ReadonlyAbility ability : character.getAbilities()) {
      if (lastIdentifier(ability.name()).equals(abilityName))
        return true;
    }
    return false;
  }

  private boolean hasAbility(EnemyCharacter character, String abilityName) {
    for (ReadonlyAbility ability : character.getAbilities()) {
      if (lastIdentifier(ability.name()).equals(abilityName))
        return true;
    }
    return false;
  }

  private int goalX = 5;
  private int goalY = 5;

  @Override
  public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {

    /* If we're at the goal square, pick a new one. */
    if (goalX == character.getLocation().getX() &&
        goalY == character.getLocation().getY()) {
      int i = getRandom().nextInt(5);
      goalX = i < 2 ? 1 : i > 2 ? 9 : 5;
      goalY = i == 2 ? 5 : (i % 2) == 1 ? 1 : 9;
    }

    {
      int bestDistance = 99999;
      /* If there are visible enemies, place the goal square under the closest enemy to
         the team. */
      for (Point2D enemyLocation : visibleEnemies.keysView()) {
        int distance = 0;
        for (ReadonlyCharacter ally : team) {
          Point2D allyLocation = ally.getLocation();
          distance +=
            (allyLocation.getX() - enemyLocation.getX()) *
            (allyLocation.getX() - enemyLocation.getX()) +
            (allyLocation.getY() - enemyLocation.getY()) *
            (allyLocation.getY() - enemyLocation.getY());
        }
        if (distance < bestDistance) {
          goalX = enemyLocation.getX();
          goalY = enemyLocation.getY();
          bestDistance = distance;
        }
      }
    }

    /* We use a priority rule for actions. */
    int bestPriority = -2;
    ReadonlyAction bestAction = null;
    for (ReadonlyAction action : actions) {
      int priority = 0;
      if (lastIdentifier(action.getName()).equals("Slice")) {
        int damagePotential = 35;
        /* We use these abilities with very high priority to /kill/ an enemy
           who's weak enough to die from the damage. If they wouldn't die,
           we still want to attack them, but we might prefer to attack
           other enemies instead. The enemy on the goal square (if any)
           is a slightly preferred target, to encourage the team to focus
           on a single enemy. */
        ReadonlyCharacter chosenTarget = null;
        for (ReadonlyCharacter target : action.availableTargets()) {
          if (!isEnemy(target))
            continue;
          chosenTarget = target;
          if (target.getHealth() <= damagePotential) {
            priority = 18;
          } else
            priority = 14;
          if (target.getLocation().getX() == goalX &&
              target.getLocation().getY() == goalY)
            priority++;
        }
        if (chosenTarget == null)
          continue;
        action.setTarget(chosenTarget);
      } else if (lastIdentifier(action.getName()).equals("Smile")) {
        priority = 0;
      } else if (action.movementAction()) {
        /* Move towards the goal location. */
        int bestDistance = 99999;
        Point2D bestLocation = null;
        priority = 1;
        for (Point2D location :
               action.availableLocations().toList().shuffleThis(getRandom())) {
          int distance =
            (location.getX() - goalX) * (location.getX() - goalX) +
            (location.getY() - goalY) * (location.getY() - goalY);
          if (distance < bestDistance) {
            bestDistance = distance;
            bestLocation = location;
          }
        }
        if (bestLocation == null)
          continue;
        action.setLocation(bestLocation);
      } else
        throw new RuntimeException("unknown action" + action.getName());

      if (priority > bestPriority) {
        bestPriority = priority;
        bestAction = action;
      }
    }
    if (bestAction == null)
      throw new RuntimeException("no action?");

    return bestAction;
  }
}

2

ダークアブソーバー

Dark Absorbersは2人の兄弟であり、犠牲者の生命力を吸収します。

  • Oracle Absorber(見えない敵を見ることができます)
    • STR: 25; AGI: 5; INT: 5
    • TrueSight柔軟遠隔吸収
  • クイックアブソーバー(兄弟よりもさらに速く吸収できます)
    • STR: 25; AGI: 5; INT: 5
    • 迅速柔軟遠隔吸収

彼らは常に成長する暗闇の雲を伴っています。臨界質量に達すると、敵を殺し始めます。

  • 闇雲
    • STR: 5; AGI: 5; INT: 25
    • クローンザップダークネス

ここに存在しないキャラクターを少なくとも1つ追加する限り、チームのここから1つのキャラクターを再利用できます。

DarkAbsorbers.java
import java.util.Arrays;
import java.util.List;

import org.eclipse.collections.api.map.ImmutableMap;
import org.eclipse.collections.api.set.ImmutableSet;

import com.nmerrill.kothcomm.game.maps.Point2D;

import fellowship.abilities.ActionAbility;
import fellowship.abilities.ReadonlyAbility;
import fellowship.abilities.attacking.Absorb;
import fellowship.abilities.attacking.Flexible;
import fellowship.abilities.attacking.Ranged;
import fellowship.abilities.vision.Darkness;
import fellowship.abilities.vision.TrueSight;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.attacking.Quick;
import fellowship.actions.damage.Zap;
import fellowship.actions.defensive.ForceField;
import fellowship.actions.other.Clone;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;

public class DarkAbsorbers extends SleafarPlayer {
    private ReadonlyCharacter zapTarget = null;

    private CharacterTemplate oracleAbsorberTemplate() {
        return new CharacterTemplate(20, 0, 0,
                new TrueSight(), new Flexible(), new Ranged(), new Absorb());
    }

    private CharacterTemplate quickAbsorberTemplate() {
        return new CharacterTemplate(20, 0, 0,
                new ActionAbility(Quick::new), new Flexible(), new Ranged(), new Absorb());
    }

    private CharacterTemplate darknessCloudTemplate() {
        return new CharacterTemplate(0, 0, 20,
                new ActionAbility(Clone::new), new ActionAbility(Zap::new), new Darkness());
    }

    @Override
    public List<CharacterTemplate> createCharacters() {
        return Arrays.asList(oracleAbsorberTemplate(), quickAbsorberTemplate(), darknessCloudTemplate());
    }

    private class Absorber extends Character {
        protected Absorber(ReadonlyCharacter delegate) {
            super(delegate);
        }

        @Override
        protected ReadonlyAction choose() {
            ReadonlyAction quick = getAction(Quick.class);

            if (quick != null && setSliceTarget(quick, 100.0)) {
                return quick;
            }
            if (slice != null && setSliceTarget(slice, 100.0)) {
                return slice;
            }

            ImmutableMap<Point2D, Double> damage = getEnemySliceDamage();
            ImmutableSet<Point2D> above5Damage = damage.select((k, v) -> v > 5.0).keysView().toSet().toImmutable();

            if (step != null && (above5Damage.contains(getLocation()) ||
                    (getHealth() <= 5.0 && isInEnemySliceRange())) && setAvoidEnemiesLocation(step)) {
                return step;
            }
            if (quick != null && setSliceTarget(quick, 0.01)) {
                return quick;
            }
            if (slice != null && setSliceTarget(slice, 0.01)) {
                return slice;
            }
            if (step != null && getSliceLocations().notEmpty() && setClosestLocation(step, getSliceLocations())) {
                return step;
            }
            if (step != null && setExploreLocation(step)) {
                return step;
            }
            return smile;
        }
    }

    private class DarknessCloud extends Character {
        private int zapCooldown = 0;
        private boolean zapNow = false;
        private boolean zapLater = false;

        protected DarknessCloud(ReadonlyCharacter delegate) {
            super(delegate);
        }

        private void updateZapFlags(double mana) {
            zapNow = zapCooldown == 0 && mana >= 15.0;
            zapLater = mana + 5 * getManaRegen() >= (zapNow ? 30.0 : 15.0);
        }

        private boolean isZappable(ReadonlyCharacter c, int zapNowCount, int zapLaterCount) {
            int forceFieldNow = 0;
            int forceFieldLater = 0;
            for (ReadonlyAbility a : c.getAbilities()) {
                if (a.abilityClass().equals(ForceField.class)) {
                    forceFieldNow = a.getRemaining();
                    forceFieldLater = 5;
                }
            }
            return c.getHealth() + c.getHealthRegen() <= (zapNowCount - forceFieldNow) * 30.0 ||
                    c.getHealth() + c.getHealthRegen() * 6 <= (zapNowCount + zapLaterCount - forceFieldNow - forceFieldLater) * 30.0;
        }

        @Override
        protected ReadonlyAction choose() {
            ReadonlyAction clone = getAction(Clone.class);
            ReadonlyAction zap = getAction(Zap.class);

            zapCooldown = zapCooldown > 0 ? zapCooldown - 1 : 0;
            updateZapFlags(getMana());
            int zapNowCount = characters.count(c -> c instanceof DarknessCloud && ((DarknessCloud) c).zapNow);
            int zapLaterCount = characters.count(c -> c instanceof DarknessCloud && ((DarknessCloud) c).zapLater);

            if (zap != null) {
                if (zapTarget != null && (!zap.availableTargets().contains(zapTarget) || zapTarget.isDead() ||
                        !isZappable(zapTarget, zapNowCount, zapLaterCount))) {
                    zapTarget = null;
                }
                if (zapTarget == null) {
                    zapTarget = chooseSmallest(zap.availableTargets().reject(c ->
                            isBear(c) || !isZappable(c, zapNowCount, zapLaterCount)), HEALTH_COMPARATOR);
                }
                if (zapTarget != null) {
                    zapCooldown = 5;
                    zapNow = false;
                    zap.setTarget(zapTarget);
                    return zap;
                }
            }

            ImmutableMap<Point2D, Double> damage = getEnemySliceDamage();
            ImmutableSet<Point2D> above5Damage = damage.select((k, v) -> v > 5.0).keysView().toSet().toImmutable();

            if (clone != null) {
                if (visibleEnemies.isEmpty()) {
                    if (setFarthestLocation(clone, getTeamHiddenLocations())) {
                        updateZapFlags(getMana() - 100.0);
                        return clone;
                    }
                } else {
                    if (setFarthestLocation(clone, above5Damage, getEnemyLocations()) ||
                            setLocation(clone, chooseSmallest(clone.availableLocations(),
                            (o1, o2) -> Double.compare(damage.get(o1), damage.get(o2))))) {
                        updateZapFlags(getMana() - 100.0);
                        return clone;
                    }
                }

                return clone;
            }
            if (step != null && (above5Damage.contains(getLocation()) ||
                    (getHealth() <= 5.0 && isInEnemySliceRange())) && setAvoidEnemiesLocation(step)) {
                return step;
            }
            if (slice != null && setSliceTarget(slice, 0.01)) {
                return slice;
            }
            if (step != null && !visibleEnemies.isEmpty() &&
                    setFarthestLocation(step, getEnemySliceLocations(), getEnemyLocations())) {
                return step;
            }
            return smile;
        }
    }

    @Override
    protected Character createCharacter(ReadonlyCharacter delegate) {
        if (hasAbility(delegate, Absorb.class)) {
            return new Absorber(delegate);
        } else if (hasAbility(delegate, Darkness.class)) {
            return new DarknessCloud(delegate);
        } else {
            throw new IllegalArgumentException();
        }
    }
}

0

LongSwordv2

「あなたは走ることはできますが、隠すことはできません...」- LongSwordv2

用途遠距離柔軟迅速TrueSight

このボットは、Strongの代わりにTrueSightを使用することを除いて、LongSwordv2 とまったく同じです。

目に見えないボットの増加を見て、多くのボットに検出されないため、それらを取り出すことに焦点を当てたボットを作成することにしました。LongSwordv2は、長距離で柔軟なスライス範囲とダブルスライスActionAbilityにより、敵キャラクターがスライス範囲に入る前に大きなダメージを与えることができます。そして、テスト段階では、ほとんどの場合、目に見えないキャラクターを中心とするチームに勝ちます。

LongSwordv2.java
import fellowship.*;
import com.nmerrill.kothcomm.game.maps.Point2D;
import fellowship.abilities.ActionAbility;
import fellowship.abilities.attacking.Flexible;
import fellowship.abilities.attacking.Ranged;
import fellowship.abilities.stats.Strong;
import fellowship.abilities.vision.Darkness;
import fellowship.abilities.vision.TrueSight;
import fellowship.actions.ReadonlyAction;
import fellowship.actions.attacking.Quick;
import fellowship.characters.CharacterTemplate;
import fellowship.characters.ReadonlyCharacter;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

public class LongSwordv2 extends Player{
    //debugging
    private boolean debug = false;
    private void println(String text) {
        if(debug)
            System.out.println(text);
    }

    //variables use to hold the start Y coordinate of the bot
    private boolean started = false;
    private int startY = 5;

    private boolean together = false;

    @Override
    public List<CharacterTemplate> createCharacters() {
        List<CharacterTemplate> templates = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            templates.add(new CharacterTemplate(20, 0, 0,
                    new Ranged(), //Adds 1 to the range of Slice
                    new Flexible(), //Can Slice in any of the 8 directions
                    new ActionAbility(Quick::new), //Slice twice, Mana: 3, Cooldown: 0
                    new TrueSight())); //Reveals all hidden units within range 2 at turn start
        }
        return templates;
    }

    @Override
    public ReadonlyAction choose(Set<ReadonlyAction> actions, ReadonlyCharacter character) {
        if(!started) {
            startY = character.getLocation().getY(); //giving startY the value of the bot's starting y-value
            started = true; //do this only once, that's why there is the if statement
        }

        ReadonlyAction current = null;

        //choosing action depending on priority
        int priority = Integer.MAX_VALUE;
        for(ReadonlyAction action:actions) {
            int priorityLocal = getPriority(action, character);
            if(priorityLocal < priority) {
                current = action;
                priority = priorityLocal;
            }
        }

        if (current == null){
            throw new RuntimeException("No valid actions");
        }

        println(current.getName());

        if(current.needsLocation()) {
            if(visibleEnemies.isEmpty()) {
                if (character.getHealth() < 100) {
                    //if has low health, go backwards towards "base"
                    //println("lowHealth");
                    current.setLocation(move(current, character, "backward"));
                } else {
                    //else go forwards to enemy's "base"
                    current.setLocation(move(current, character, "forward"));
                }
            }else{
                //go towards closest enemy
                current.setLocation(current.availableLocations().minBy(p1->p1.cartesianDistance(visibleEnemies.keysView().minBy(p1::cartesianDistance))));
            }
        }
        if(current.needsTarget()) {
            //get closest target
            current.setTarget(current.availableTargets().minBy(p1 -> 0));
        }

        Iterator<ReadonlyCharacter> iterator = current.availableTargets().iterator();

        while(iterator.hasNext()) {
            Point2D loc = iterator.next().getLocation();
            println(loc.getX()+","+loc.getY());
        }

        return current;
    }

    //move backwards or forwards
    private Point2D move(ReadonlyAction readonlyAction, ReadonlyCharacter character, String direction) {
        Point2D location = null;

        //move direction depending on Y coordinate of point
        for(Point2D point2D:readonlyAction.availableLocations()) {
            switch (direction) {
                case "forward":
                    if(startY > 5) { //bot started at bottom
                        if (point2D.getY() < character.getLocation().getY())
                            location = point2D;
                    }else{ //bot started at top
                        if (point2D.getY() > character.getLocation().getY())
                            location = point2D;
                    }
                    break;
                case "backward":
                    if(startY > 5) { //bot started at bottom
                        if (point2D.getY() > character.getLocation().getY())
                            location = point2D;
                    }else{ //bot started at top
                        if (point2D.getY() < character.getLocation().getY())
                            location = point2D;
                    }
                    break;
            }

        }

        //if no available locations, just choose the first available location
        if(location == null) {
            location = readonlyAction.availableLocations().iterator().next();
        }

        println(location.getY()+","+character.getLocation().getY());

        return location;
    }

    private int getPriority(ReadonlyAction action, ReadonlyCharacter character) {
        if(visibleEnemies.isEmpty()) {
            //if there are no visible enemies, Step. In the choose function, this becomes move forward or backward depending on health
            if(action.getName().equals("Step")) {
                return 100;
            }
        }else {
            /*
             * PRIORITIES:
             *  1. Quick (Slice twice)
             *  2. Slice
             *  3. Step (when enemy is not in range --> move towards enemy)
             */
            if (action.getName().equals("Quick")) {
                return 1;
            }else if(action.getName().equals("Slice")) {
                return 10;
            }else if(action.getName().equals("Step")) {
                return 50;
            }
        }
        //Kids, don't Smile, instead Step or Slice
        return 1000;
    }
}

ヘッダーが欠落しているため、このボットのダウンロードは失敗します。
-Sleafar

@Sleafar行って...追加しました!
Kritixi Lithos
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.