Trận chiến học bổng KotH


45

Trong thử thách này, bạn sẽ tạo ra một học bổng với mục tiêu đánh bại tất cả các học bổng khác trong trận chiến.

Một nhóm (nhóm) bao gồm 3 nhân vật . Mỗi nhân vật di chuyển độc lập với phần còn lại của đội của họ, nhưng họ sẽ cần phải làm việc cùng nhau khi chiến đấu với kẻ thù của bạn. Các đội sẽ đối đầu với nhau theo cách xoay vòng. Thắng có giá trị 3 điểm, ràng buộc có giá trị 1 điểm và thua lỗ có giá trị 0 điểm.

Nhân vật có khả năng. Sự lựa chọn những khả năng mà nhân vật của bạn có là một trong những phần quan trọng nhất (và thú vị) trong KotH này . Chúng đều mạnh, và có khả năng quét sạch kẻ thù của bạn.

Nhân vật có Điểm Sức khỏe (HP) và khi HP của họ chạm (hoặc xuống dưới) 0, họ sẽ chết . Nếu tất cả các nhân vật trong đội của đối thủ của bạn chết, thì bạn sẽ thắng!

Nhân vật có Mana. Hầu hết các hành động đều yêu cầu Mana thực hiện và nếu bạn không có đủ, hành động đó không có sẵn cho bạn.

Nhân vật có độ trễ . Điều này xác định số lượng đánh dấu giữa mỗi lượt (bắt đầu từ 100). Thấp hơn là tốt hơn.

Nhân vật có thuộc tính . Mỗi nhân vật có cơ sở 5 trong mỗi thuộc tính và bạn được thêm 20 điểm thuộc tính để phân chia. Sau khi gán điểm thuộc tính, thuộc tính chính của bạn được đặt làm thuộc tính cao nhất của bạn.

Các thuộc tính có sẵn là:

  • Sức mạnh: Cung cấp 10 HP tối đa và 0,5 HP mỗi lượt
  • Thông minh: Cung cấp 7 Max Mana và .1 Mana mỗi lượt
  • Nhanh nhẹn: Giảm độ trễ lần lượt 1

Chuyển động, Tầm nhìn, Phạm vi Phạm
vi như sau (tập trung quanh 0). Một số phạm vi là hồng y , có nghĩa là chúng chỉ có thể đi trực tiếp lên, trái, phải hoặc xuống.

    444
   43334
  4322234
 432111234
 432101234
 432111234
  4322234
   43334
    444

Các nhân vật có tầm nhìn bắt đầu là 2. Tầm nhìn giữa những người chơi có cùng mối quan hệ được chia sẻ.

Cách chơi

Xây dựng
Người chơi sẽ xây dựng học bổng của họ. Bạn cần làm các bước sau :

  1. Cho mỗi điểm thuộc tính nhân vật . Mỗi nhân vật bắt đầu với 5 trong mỗi chỉ số, có thêm 20 để phân phối giữa 3.

  2. Cung cấp cho mỗi nhân vật khả năng . Mỗi nhân vật bắt đầu với 4 vị trí khả năng và các khả năng mặc định có 1 vị trí. Một số khả năng có thể lặp lại , và có thể được trao cho một nhân vật nhiều lần. Không được phép sử dụng bộ khả năng gửi khác mà không có sự cho phép của chủ sở hữu.

  3. Viết một số mã cho bot của bạn. Mã phải ở trong Java và sẽ được sử dụng để chiến đấu (bước tiếp theo)

Hành động

Tất cả các nhân vật bắt đầu với 3 hành động tiêu chuẩn:

  1. Bước : Di chuyển nhân vật của bạn trong phạm vi chính yếu là 1
  2. Slice : Tấn công kẻ thù cho Sơ cấp trong phạm vi chính là 1
  3. Cười : Không làm gì cả

Đến lượt của một nhân vật, sau đó phải chọn một hành động để thực hiện. Các hành động có thể có chi phí Mana và có thể có Thời gian hồi chiêu, xác định số lượt bạn phải chờ trước khi thực hiện lại hành động đó.

Khả năng
Mỗi nhân vật có 4 khe khả năng. Nếu một khả năng in nghiêng, đó là một hành động.

Khả năng

Tên Mô tả Mana Cooldown 
                                                     Mobility                                                     

Nháy mắt         Di chuyển đến một hình vuông, phạm vi 4 2 2 
 Hoán          đổi vị trí Hoán đổi với Target 5 5 
 Dịch      chuyển tức thời Di chuyển bất cứ nơi nào 20 5

Dash Tăng phạm vi của bước lên 1. Lặp lại                                               
Bước di động có thể di chuyển theo bất kỳ hướng nào trong 8 hướng                                                  
                                                    Tấn công                                                    

        Slice nhanh hai lần 3 0 
 Dệt         Slice tất cả kẻ thù có thể nhìn thấy một lần 15 10

Hấp thụ Mỗi lát cắt đánh cắp 1 thuộc tính chính của mục tiêu của bạn. Kéo dài 20 lượt                    
Cleave Mỗi lát cắt gây sát thương 1/2 cho kẻ địch liền kề                                           
Critital Thêm 30% cơ hội cho Slice gây sát thương 200%. Có thể lặp lại                               
Feast Mỗi lát cắt tăng HP của bạn lên 3. Lặp lại                                             
Linh hoạt có thể cắt theo bất kỳ trong 8 hướng                                                      
Mana ăn cắp Slice ăn cắp 2 mana. Có thể lặp lại                                                           
Phản xạ cắt lát khi cắt 0 3 
Phạm vi Thêm 1 vào phạm vi của Slice                                                              
Vuốt Mỗi lát cắt liên tiếp trên cùng một mục tiêu sẽ gây sát thương nhiều hơn 3 lần so với lần trước               
                                                    Trạng thái                                                     

Xua tan        tất cả các trạng thái khỏi một mục tiêu. Phạm vi 2. 20 10 
 Duel          Đóng băng bạn và mục tiêu của bạn cho đến khi một trong hai bạn chết. Phạm vi 1 25 0 
 Knockout      Bạn và mục tiêu bị choáng trong 1000 tới ticks 10 10 
 Meteor        Tất cả kẻ thù làm choáng váng cho tới 100 ticks 25 10 
 Leash         Target được đông lạnh trong 2 lượt tiếp theo của họ 4 6 
 Poison        Độc kẻ thù trong vòng 1 HP cho 5 vòng xoắn 5 0 
 Im lặng      Mục tiêu được im lặng trong 5 lượt 5 7 
 chậm          mục tiêu bị chậm 40 ve trong 3 lượt tiếp theo của họ 10 5 
 Stun          mục tiêu bị choáng cho tới 300 ticks 10 10

Lạnh Tất cả các nhân vật khác trong phạm vi 2 bị làm chậm 10 tích tắc                                
Miễn dịch Không có trạng thái có thể được áp dụng cho bạn                                                           
                                                    Phòng ngự                                                    

Lực lượng Trường   chặn 5 nguồn sát thương tiếp theo. Không stack 15 5 
 Ghost         Trong một lượt, tất cả sát thương sẽ hồi phục 10 10 
 Heal          Heal Target cho 20 HP 10 3 
 Khôi phục       Tất cả các đơn vị được khôi phục lại toàn bộ sức khỏe 20 40 
 Shield        Bạn không thể bị cắt cho đến lượt tiếp theo 3 0

Trốn 25% cơ hội để một Slice không đánh bạn. Có thể lặp lại                                         
Trụ cột chỉ có thể được cắt một lần lượt                                                            
Hồi sinh Khi bị giết, hãy sống lại với đầy đủ HP (và không có trạng thái) 0 40 
Gai khi gây sát thương, giảm một nửa sát thương                                           
                                                     Tầm nhìn                                                      

        Đội Cloak trở nên vô hình trong 5 lượt 20 20 
 Ẩn          Bạn vô hình trong 5 lượt 4 7 
 Pha         Trở nên vô hình trong 1 lượt 0 3 
         Mục tiêu theo dõi không thể tàng hình và nhận thêm 10% sát thương. Kéo dài 10 lượt. 5 5

Bóng tối Tầm nhìn của kẻ thù giảm đi 1. Ngăn xếp, nhưng không thể xuống dưới 1.                                 
Tầm nhìn xa Tầm nhìn tăng thêm 2. Có thể lặp lại                                                    
Vô hình Bạn vô hình nếu bạn bắt đầu rời khỏi tầm nhìn của kẻ thù                               
Tầm nhìn chân thực Tiết lộ tất cả các đơn vị ẩn trong phạm vi 2 khi bắt đầu                                     
                                                     Hư hại                                                      

Drain         Gây 5 sát thương cho Target và tự hồi máu cho 5 HP trong khi chúng ở trong 1 phạm vi 10 5 
 Lightning     gây 15 sát thương cho tất cả kẻ địch 20 10 
 K / O           Giết mục tiêu nếu mục tiêu dưới 20% HP 20 0 
 Bẫy          Đặt bẫy vô hình. Bẫy gây 15 sát thương khi bước lên. Ngăn xếp. 10 2 
 Zap           Gây 30 sát thương cho mục tiêu 30 5

Gây tĩnh 5 sát thương mỗi lượt cho tất cả kẻ địch trong phạm vi 1. Có thể lặp lại                       
                                                      Số liệu thống kê                                                      

Người sói      Thêm 10 vào tất cả các chỉ số trong 5 lượt 30 25

Buff nhân đôi số lượng HP của bạn. Có thể lặp lại                                                           
Hành động thông minh có thời gian hồi chiêu ngắn hơn 20%. Có thể lặp lại                                             
Tập trung Tăng tỷ lệ hồi quy Mana của bạn thêm Int / 10. Có thể lặp lại                                  
Tái tạo Tăng tỷ lệ hồi sinh của bạn bằng Sức mạnh / 2. Có thể lặp lại                                 
Hành động thông minh tốn 2 mana. Có thể lặp lại                                                      
Mạnh Bạn đạt được 10 điểm thuộc tính. Có thể lặp lại                                                  
Yếu Bạn mất 15 điểm thuộc tính. Bạn có được 2 vị trí khả năng (cái này cần một trong số chúng)                  
                                                      Khác                                                      

Bear          Có thể triệu tập một con gấu có 5 con trong mỗi stat 8 10 
 Clone         Clone mình. Có hai khe khả năng. 100 100 
 Trộm         Thay thế hành động này bằng mục tiêu kẻ thù hành động cuối cùng được sử dụng. Kéo dài 10 lượt 5 0 
 Tường          Tạo một bức tường không thể xuyên thủng trên ô vuông trống được nhắm mục tiêu, phạm vi 6 10 10 

Trạng thái:

  • Stun cho phép nhân vật của bạn để chỉ thực hiện hành động Nụ cười, và kéo dài X ve .
  • Đóng băng ngăn nhân vật của bạn di chuyển, và kéo dài X lượt.
  • Im lặng ngăn nhân vật của bạn thực hiện bất cứ điều gì ngoài Nụ cười, Bước hoặc Lát và kéo dài lượt X.
  • Poison gây sát thương cho nhân vật của bạn để gây sát thương X cho lượt Y. Nếu bạn áp dụng một chất độc khác, thiệt hại cộng lại và thời gian được làm mới.
  • Slow thêm X vào số lượng tick giữa các lượt của bạn. Nó không ảnh hưởng đến lượt sắp tới của bạn , chỉ lần lượt sau.
  • Vô hình làm cho nó để bạn không thể nhìn thấy hoặc bị thiệt hại bởi đối thủ của bạn. Nếu bạn thực hiện bất kỳ hành động nào ngoài Bước hoặc Nụ cười, nó sẽ bị xóa. Nếu đối thủ của bạn có một khả năng giúp họ có tầm nhìn về bạn, khả năng tàng hình sẽ bị loại bỏ.

Tất cả các trạng thái (trừ Poison) hoạt động độc lập với nhau.

Ghi chú bên:

  • Nếu có một ràng buộc cho thuộc tính chính, nó được giải quyết dưới dạng STR> AGI> INT.
  • Bạn chơi trên lưới 10x10. Các đội sẽ được đặt ở phía đối diện.
  • Tỷ lệ phần trăm xếp chồng lên nhau, ngoại trừ Thông minh.

Quy tắc nộp

Bạn cần thực hiện 2 chức năng:

// 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);

Bạn cũng sẽ có quyền truy cập vào ba biến (biến thành viên):

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

Đó là nó. Dưới đây bạn có thể tìm thấy một API hoàn chỉnh, theo thứ tự bảng chữ cái:

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

Trên đây là tất cả các chức năng bạn có thể cần để gửi. Phản xạ không được phép. Nếu việc gửi không hợp lệ vì bất kỳ lý do gì, vui lòng xóa nó hoặc thêm "Không hợp lệ" vào tiêu đề. Trình của bạn không nên có một tuyên bố gói. Nội dung gửi của bạn phải được chứa trong khối mã nhiều dòng đầu tiên và dòng đầu tiên phải có tên tệp.

Cách chạy dự án:

Có một số cách:

  1. Tải xuống tệp JAR và chạy java -jar Fellowship.jar. Nếu bạn muốn tải về các bài nộp khác, vượt qua -q 99744. java phải trỏ đến JDK, không phải JRE.
  2. Nhân bản repo git , và chạy gradle run. Bạn cần cài đặt lớp và nếu bạn muốn truyền đối số, hãy sử dụng-PappArgs="['arg1', 'args2']"
  3. Nhân bản repo git , và tự biên dịch nó. Bạn sẽ cần các thư viện sau: org.eclipse.collections:eclipse-collections-api:8.0.0, org.eclipse.collections:eclipse-collections:8.0.0, com.beust:jcommander:1.48, com.google.code.gson:gson:2.7,org.jsoup:jsoup:1.9.2

Nếu bạn sao chép, bạn phải sử dụng --recursivecờ và khi bạn kéo các bản cập nhật, bao gồm--recurse-submodules Đối với bất kỳ điều nào ở trên, lớp của bạn cần phải đi vào submissions/javathư mục. Nếu bạn đang sử dụng gradle, hoặc tự biên dịch nó, bạn có thể đặt lớp trong chính dự án. Bạn sẽ cần bỏ ghi chú một số dòng trong hàm main và cập nhật chúng để trỏ đến lớp của bạn.

Bảng điểm:

+------+-------------------+-------+
| 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 |
+------+-------------------+-------+

Nếu bạn có bất kỳ câu hỏi nào, hoặc cần giúp đỡ, hãy bình luận bên dưới hoặc tham gia phòng chat ! Chúc may mắn và vui vẻ


1
Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Dennis

Sét được liệt kê là Deal 15 damage to all enemies, nhưng kẻ thù vô hình không bị ảnh hưởng bởi sét. Đây có phải là một lỗi?
Mặt

1
Thử thách này đặc biệt phức tạp hơn những thử thách trước đó. Tôi ước có một định dạng ở đây làm cho một cái gì đó như thế này cạnh tranh hơn trong thời gian dài.
Sparr

2
Đúng, không biết các tùy chọn -g, tuy nhiên khi tôi đang phát triển bot của mình, tôi đã không có nó ở trạng thái có thể sử dụng được nên tôi bắt đầu thực hiện một thay thế. Nó rất thô sơ vào lúc này nhưng nó có bán kính nhìn thấy được. Dưới đây là bản chụp của Bear Cavalry vs Người chơi mẫu! chụp .
Moogie

1
Bạn có thể kiểm tra các nhân vật mới và cập nhật điểm số?
Lemon phá hủy

Câu trả lời:


10

TĩnhCloud

Một đám mây đang phát triển gây sát thương tĩnh cho bất kỳ ai đến gần. Nó bao gồm:

  • 1/3 phần vô hình
    • STR: 5; AGI: 5; INT: 25
    • Nhân bản , Vô hình , Tĩnh
  • 2/3 phần nhìn thấy được
    • STR: 5; AGI: 5; INT: 25
    • Bản sao , tĩnh , tĩnh

Bạn có thể sử dụng lại các ký tự đơn từ đây trong nhóm của mình, miễn là bạn thêm ít nhất một ký tự không có ở đây.

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
Điều này rất thú vị vì Tĩnh không phá vỡ khả năng tàng hình.
Draco18

7

Trình phát mẫu

Sử dụng Ranged , Linh hoạt , ZapKO . Bạn có quyền sử dụng bộ khả năng này nếu bạn muốn.

Hãy sử dụng bot này làm mẫu để tạo riêng của bạn.

Hãy nhớ rằng bạn cần thay đổi tên tệp trên dòng đầu tiên, cũng như chọn bộ khả năng của riêng bạn.

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

Hèn nhátSniperMk2

Sử dụng Zap , FarSight * 2 và Ẩn .

Bot này là một kẻ hèn nhát. Ưu tiên cao nhất của nó là không được nhắm mục tiêu. Cuối cùng, anh ta sử dụng tầm nhìn vượt trội của mình để xem kẻ thù đang ở đâu. Nó sử dụng kiến ​​thức này để tránh bị nhìn thấy trong khi theo dõi / theo dõi kẻ thù mà không bị nhìn thấy. Nếu nó được nhìn thấy, hoặc có thể được nhìn thấy trong lượt tiếp theo thì bot sẽ 'ẩn' trở nên vô hình trong một thời gian.

Trong chế độ theo dõi, và có đủ mana và thiết lập thời gian hồi chiêu, sau đó sẽ 'Zap' kẻ thù có thể nhìn thấy yếu nhất.

Một khi mana giảm xuống 10% thì sẽ né tránh kẻ thù cho đến khi mana được phục hồi. Bằng cách này, nó có thể hạ gục nhanh nhất có thể trên kẻ thù bị theo dõi. Hy vọng phủ nhận bất kỳ HP regen nào kẻ thù có.

Lưu ý vì 'Zap' là phạm vi vô hạn, tất cả các thành viên trong nhóm sẽ nhắm mục tiêu vào cùng một bot khi hạ gục.

Tôi có các phương án khác của cùng một ý tưởng cơ bản mà tôi có thể thêm vào dưới dạng câu trả lời: tất cả chúng đều có lợi ích / bất lợi khác nhau được khai thác / phơi bày tùy thuộc vào các đối thủ hiện tại.

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;
    }
}

Tên tệp phải là CowardlySniperMk2 :)
Nathan Merrill

Rất tiếc: P cắt và dán làm cho tôi trông giống như một kẻ ngốc!
Moogie

Nhắc tôi nhớ về
Mario Dart

@KritixiLithos Nhắc tôi về Splzoon's Bamboozler 14 Mk II. ;)
TNT

7

Xác sống

Zombie, mọi người đều biết chúng. Họ ở trong một nhóm không làm gì cho đến khi ai đó xuất hiện. Chúng rất khó để giết, và cho dù bạn có giết bao nhiêu thì vẫn luôn có nhiều hơn. Và chúng thường xuất hiện từ hư không ngay sau lưng bạn.

  • 1 x Zombie # 1 (mạnh nhất và do đó là zombie alpha)
    • STR: 25; AGI: 5; INT: 15
    • Nhân bản , hồi sinh , mạnh mẽ
  • 2 x Zombie # 2 (không ai muốn trở thành Zombie # 3 trong các khoản tín dụng kết thúc, do đó cả hai đều có cùng số)
    • STR: 15; AGI: 5; INT: 15
    • Nhân bản , hồi sinh , hấp thụ

Bạn có thể sử dụng lại các ký tự đơn từ đây trong nhóm của mình, miễn là bạn thêm ít nhất một ký tự không có ở đây.

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);
    }
}

Tôi không thể làm cho các bot của bạn hoạt động ... lớp cơ sở Sleafar nhập khẩu học bổng. Nhân vật.CharacterInterface dường như không có trong nhánh chính mới nhất ...
Moogie

@Moogie Nathan đã khắc phục sự cố .
Sleafar

7

Bất khả xâm phạm

Một nhóm các chiến binh mạnh mẽ có thể sống sót trong hầu hết mọi thứ. Điều này dẫn đến rất nhiều thời gian chờ, điều không may là tôi thường không giành chiến thắng. Tuy nhiên, không có trận đấu nào tôi thấy là không thể tưởng tượng được và khi đội thua cuộc, điều đó thường xuyên xảy ra với các nhân vật vẫn còn sống sót.

Trận đấu khó khăn nhất của đội này là chống lại Kỵ binh gấu (họ thực sự không có khả năng quét sạch đội này, nhưng thường giành chiến thắng trong trận đấu bù do số lượng tuyệt đối của họ); Rogue Squad (đội có phần yếu để đầu độc); và Ma cà rồng (Tôi không hoàn toàn chắc chắn tại sao).

Trong các mô phỏng, nhóm gần như luôn luôn đứng đầu hoặc thứ hai. Điểm của nó khá ổn định; người chiến thắng thường phụ thuộc vào việc Rogue Squad làm tốt như thế nào so với các đối thủ khác (vị trí của nó ngẫu nhiên hơn rất nhiều so với Invulnerables ').

Pháp sư trong lĩnh vực độc hại

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

Pháp sư tự bảo vệ mình bằng cách sử dụng kết hợp Trụ cộtTrường lực ; Được tập trung và có Trí thông minh cao, nó sẽ tái tạo đủ mana để sử dụng Trường lực trong thời gian hồi chiêu mỗi lần. Giả sử rằng các đối thủ không có Agility được tăng cường, do đó họ có thể tấn công nó với tối đa năm Lát mỗi năm lượt và năm nguồn sát thương sẽ bị chặn trong khoảng thời gian đó. Nói cách khác, Pháp sư hoàn toàn yêu cầu phép thuật để đánh bại; Cắt lát không tự hoạt động cho dù bạn có giỏi đến đâu.

Pháp sư có thể tấn công thông qua Cắt lát trong 25 trong trường hợp khẩn cấp, nhưng chủ yếu là sử dụng Poison nhiều lần, điều này rất dễ gây ra với khả năng tái tạo MP ở mức cao này. Vì Poison có phạm vi vô hạn, chỉ bị giới hạn bởi tầm nhìn của đội, Mage là cách nhóm này đánh bại những kẻ thù rất nặng hoặc tái sinh; Phần còn lại của đội duy trì tầm nhìn về họ, trong khi Mage gây ra lượng sát thương bậc hai. Chất độc cuối cùng chắc chắn sẽ vượt xa khả năng phục hồi HP của chúng và tôi không phải lo lắng về thiệt hại tĩnh như Spike trong quá trình này.

Chim ưng khổng lồ Clifftop

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

Công việc chính của Falcon là đưa phần còn lại của tầm nhìn về các mục tiêu, để chúng có thể bị tấn công, do thám bản đồ với con mắt của một con chim ưng. Tầm nhìn xa cung cấp đủ tầm nhìn mà kẻ thù cố gắng che giấu hoặc chạy trốn thường có thể bị mắc kẹt bởi phạm vi tầm nhìn trong một góc của bản đồ; Thật không dễ dàng gì để tạo cho Falconer sự run rẩy. True Sight là đội quân chính của đội này chống lại kẻ thù vô hình, nếu không sẽ không thể gây sát thương. Lớn, và do đó Mạnh , khiến Falconer có khả năng chống chịu sát thương rất cao và có khả năng Slice nặng khi cần thiết. Cuối cùng, Falconer có thể thả chim ưng của mình lên đám đông kẻ thù bên dưới, ra lệnh cho chúng đan xen giữa kẻ thù và gây sát thương diện tích lớn (35).

Ngoài công việc của Falcon là săn lùng kẻ thù lảng tránh và duy trì tầm nhìn của kẻ thù để Pháp sư có thể tiếp tục đầu độc chúng, khả năng thỉnh thoảng Dệt cho 35 là chìa khóa để đối phó với các đội quân địch; Có thể đánh trúng rất nhiều kẻ thù theo cách đó và để chúng đủ thấp để các thành viên còn lại kết thúc (lý tưởng nhất là với một Dệt khác). Swarming gần như là một chiến lược áp đảo theo các quy tắc này, và Dệt là một trong số ít các quầy thực sự mà nó có. Ngay cả khi đó, nó không thực sự đủ tốt.

Quỷ xương sọ

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

Công việc của Trollbone là giữ cho đám địch bị đàn áp trong khi các đơn vị khác có thể thực hiện công việc của chúng. Giống như Pháp sư, Trollbone có một phép thuật vô hạn trong Knockout . Combo này kết hợp rất tốt với Poison Poison; Nếu từng người có thể đối đầu trực diện với kẻ thù (và chống lại nhiều đội, thì), Falconer sẽ có được tầm nhìn, Trollbone sẽ làm choáng chúng, sau đó Pháp sư sẽ chất độc lên chúng, và cuối cùng chúng sẽ chết không có khả năng làm bất cứ điều gì (Knockout tồn tại 1000 tích tắc trên mục tiêu và Trollbone sẽ tái tạo thời gian hồi chiêu cho nó nhanh hơn một chút so với điều đó). Nó cũng rất tốt trong việc bảo vệ Trollbone trước những kẻ thù mạnh duy nhất; họ không thể làm gì anh ta nếu họ không có ý thức. Tất nhiên, đập vỡ hộp sọ với kẻ thù có thể khiến cả hai bị bối rối,để làm choáng và đầu độc (và một loạt các trạng thái khác không ai quan tâm). Là một nhân vật tập trung vào phép thuật, dù sao cũng không thiên về phép thuật, Trollbone tái tạo ma thuật không phải thông qua trí thông minh, mà bằng cách uống máu kẻ thù, thực hiện Mana Steal với mỗi đòn đánh; điều này cho tốc độ tái tạo MP khá tốt (và kẻ thù bị choáng khiến mục tiêu dễ dàng đánh cắp MP từ đó). Cuối cùng, Trollbone thỉnh thoảng đi trên một hung hăng và sẽ Dệt qua hàng ngũ kẻ thù trong khi đập đầu vào và uống máu của họ. Chống lại một nhóm kẻ thù đủ lớn, điều này thực sự lấy lại mana và nó có thể kết liễu một bầy mà Falconer suy yếu (25 + 35 là 60, vì vậy nó hoạt động ngay cả khi kẻ địch tái sinh ở một mức độ nào đó ở giữa).

Chiến lược

Không giống như nhiều đội, tôi tập trung rất nhiều vào AI, không chỉ là xây dựng đội nhóm. Một quy tắc cơ bản là nhóm sẽ luôn cố gắng nhóm nếu họ không bận làm việc gì khác, khiến họ khó bị bao vây hơn và có thể bảo vệ lẫn nhau. Nếu chúng bị cuốn vào, chúng sẽ cố trốn trong một góc. Mặt khác, nếu kẻ thù cố gắng chạy trốn hoặc chạy trốn, chúng sẽ đi lang thang trên bản đồ, chọn và đi đến các góc ngẫu nhiên hoặc trung tâm; điều này ít nhiều đảm bảo rằng Falconer sẽ phát hiện ra mục tiêu cuối cùng. Phong trào được thiết kế để không bao giờ để kẻ thù có được đòn tấn công đầu tiên nếu có thể; kẻ thù sẽ phải tự mình bước vào phạm vi Slice. Pháp sư sẽ luôn luônđể lại MP cho Trường lực, ngăn chặn sự cạn kiệt MP (cách duy nhất có thể thất bại là với Hấp thụ, có thể vượt qua Trường lực ngay cả khi thiệt hại không xảy ra). Đây thường không phải là một vấn đề; thông thường Mage có thể spam Poison mỗi lượt mà không gặp vấn đề gì. Nếu không bị đe dọa, nhóm nghiên cứu thích đuổi theo kẻ thù cùng một lúc, làm choáng chúng khi chúng xuất hiện, sau đó đầu độc chúng liên tục cho đến khi chúng chết. Với những kẻ thù khác xung quanh, đội sẽ cố gắng thả diều nếu có thể, chạy vòng tròn và buộc hầu hết kẻ thù phải đuổi theo, trong khi làm choáng và đầu độc một trong số chúng. Vấn đề chính là ở bầy đàn, đó là lý do tại sao có quá nhiều Dệt ở đây, nhưng ngay cả khi đó có vẻ khó thực sự đánh bại chiến lược.

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;
  }
}

rất hiệu quả :) tuyệt đẹp trông giống như một chiến lược hiệu quả!
Moogie

Nhân vật của bạn với ForceField không thể bị đánh bại bởi Noobs của tôi, mặc dù nó chỉ có 50 máu!
Kritixi Lithos

Đã sửa lỗi mà @Sleafar chỉ ra.

Tôi nghĩ rằng bạn đang có quá nhiều điểm kỹ năng trên chim ưng và troll xương của bạn
Eumel

Đã sửa quá. Đó chỉ là một lỗi đánh máy trong mô tả, mã là chính xác.

7

RogueSquad

Một đội lừa đảo bao gồm:

  • 1 Scout (ở trong bóng tối khi khám phá bản đồ)
    • STR: 5; AGI: 5; INT: 25
    • Bản sao , Vô hình , Tầm nhìn xa
  • 2 Assasin (tấn công kẻ thù bằng chất độc chết người)
    • STR: 5; AGI: 5; INT: 25
    • Bản sao , độc , tập trung

Sức mạnh lớn nhất cả hai có thể sử dụng, là gọi thêm thành viên của đội để hỗ trợ họ.

Bạn có thể sử dụng lại các ký tự đơn từ đây trong nhóm của mình, miễn là bạn thêm ít nhất một ký tự không có ở đây.

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();
        }
    }
}

Lớp cơ sở cho tất cả các bot của tôi

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();
    }
}

Hoàn thành tốt Một đội khó khăn để đánh bại ... thách thức được chấp nhận: P
Moogie

6

Ma cà rồng

Tôi chưa quen với điều này và tôi không chắc mình biết mình đang làm gì, nhưng tôi nghĩ nó có vẻ thú vị, vì vậy đây là nỗ lực của tôi.

Ma cà rồng sẽ tìm kiếm kẻ thù và nhắm vào những kẻ yếu nhất, rút ​​cạn sinh mạng khỏi chúng, đồng thời phát triển mạnh mẽ hơn và lấy lại sức khỏe của chính mình, sẵn sàng chuyển sang nạn nhân tiếp theo. Nếu chúng bị thương đáng kể, chúng sẽ cố gắng di chuyển ra xa cho đến khi sự tái sinh tự nhiên của chúng khôi phục chúng trở lại tình trạng chiến đấu.

Sử dụng Hấp thụ , Lễ , Tái tạo , Mạnh mẽ với mọi thứ trong 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;
    }
}

Tôi thích việc sử dụng tất cả các hành động thụ động! Đẹp một.
Moogie

6

Kỵ binh gấu

Sử dụng Hấp thụ , Nhân bảnGấu ; số liệu thống kê là (+9, +0, +11) .

Ở lượt đầu tiên, mọi người tạo một bản sao của chính mình, để đội có 6 nhân vật trên sân. Sau đó, họ tấn công kẻ thù, spam gấu bất cứ khi nào có thể và làm suy yếu kẻ thù của họ bằng các cuộc tấn công hấp thụ stat.

Mã này là một mớ hỗn độn, nhưng nó dường như hoạt động. Tôi đã sao chép các phần của nó từ Trình phát Mẫu.

Bạn có thể sử dụng các nhân vật của đội này theo bất kỳ cách nào bạn thích.

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())
                    );
    }
}

gấu của bạn là một chiến lược hiệu quả để chống lại kẻ bắn tỉa hèn nhát của tôi :) quá nhiều mục tiêu để bot của tôi có thể vào vị trí độc lập để bắt đầu hạ gục! làm tốt lắm
Moogie

5

Gai nhọn

Spiky, như tên của anh ta ngụ ý, không bị tấn công một cách mù quáng. Anh ta dũng mãnh, có thể tái tạo toàn bộ HP và tấn công như một chiếc xe tải. Anh ta sẽ bay lơ lửng ở trung tâm bản đồ, chờ đợi ai đó đến gần.

Sử dụng mạnh (STR 10) x2, Regenerate , Spikes và đi STR đầy đủ (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;
    }

}

> Trình của bạn không nên có một tuyên bố gói. Nội dung gửi của bạn phải được chứa trong khối mã nhiều dòng đầu tiên và dòng đầu tiên phải có tên tệp.
Kritixi Lithos

Tôi chỉ nêu những gì đã nói trong bài.
Kritixi Lithos

@KritixiLithos Đoán đó là điều duy nhất tôi đã làm đúng. Cảm ơn.
Thrax

Bot đẹp quá Spiky đã đánh bại những con bot tốt nhất của tôi.
Kritixi Lithos

Bạn có thể đổi ThreadLocalRandom.current()thành getRandom()? Nó cho phép các trò chơi được xác định.
Nathan Merrill

5

Pháp sư

Bản thân nhân bản để gây sát thương tức thì cho tất cả kẻ thù càng nhiều càng tốt với Dệt (trước đây là sét, nhưng Wea gây sát thương nhiều hơn và có chi phí mana thấp hơn.

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);        
    }
}

Nhân bản dường như là một lựa chọn phổ biến cho bot ... Tôi nên sử dụng bot của bạn làm nguồn cảm hứng.
Moogie

4

Từ dài

Sử dụng Ranged (Thêm 1 tới phạm vi của Slice), linh hoạt (Cần Slice trong bất kỳ của 8 hướng), Nhanh (Slice hai lần, Mana: 3, Cooldown: 0), Mạnh (Bạn đạt được 10 điểm thuộc tính hơn)

TRẠM

5 điểm bắt đầu là cơ sở

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

Trước hết, tôi thực sự thích làm bot này, và tôi thực sự thích KotH này (đây là lần đầu tiên tôi gửi thử thách cho KotH!). (Tôi có thể đăng thêm bot)

Bot

Bot này dựa vào khả năng Tấn công để chế ngự đối thủ. Theo như tôi đã thử nghiệm, bot này thực sự tốt với các bot có sức khỏe tương đối thấp. Ngoài ra, nó có phạm vi tấn công lớn, và có thể dễ dàng nhắm mục tiêu hầu hết (hoặc một nửa) kẻ thù trong tầm nhìn của nó.

Để so sánh bot này với vai trò NetHack, tôi sẽ nói nó giống với Valkyrie do khái niệm "LongSword" và sức khỏe trung bình.

TÊN

Bot này có phạm vi dài hơn một chút so với bot bình thường và nó có thể tấn công theo bất kỳ hướng nào. Điều này khiến tôi nhớ đến hầu hết Thanh kiếm dài trong NetHack, vì vậy tôi đã đặt tên cho bot của mình như vậy.

HÀNH VI

Nếu nhân vật không thể nhìn thấy nhân vật kẻ thù, thì nó sẽ đi về phía đối diện của trường (khu vực sinh sản của kẻ thù / "căn cứ" của kẻ thù) để tìm nhân vật của kẻ thù. Nếu nó tìm thấy kẻ thù, thì nó sẽ tấn công chúng bằng Quick, Slice (giảm mức độ ưu tiên). Nếu nó không thể nhắm mục tiêu vào kẻ thù, thì bot sẽ đi về phía nhân vật kẻ thù để tiêu diệt chúng.

Nếu nhân vật không thể nhìn thấy nhân vật kẻ thù có sức khỏe thấp, thì nó sẽ rút lui về phía "căn cứ" / khu vực sinh sản.

Lưu ý: Bot sẽ không bao giờ rút lui giữa trận chiến. Bot này sẽ không bao giờ cười.

Tôi đã sử dụng regex sau đây trên regexr.com để chuyển đổi mã Java của mình thành một khối mã được định dạng.

Mã dưới đây được nhận xét nên dễ hiểu. Nếu bạn có bất kỳ câu hỏi hoặc làm rõ về cách thức hoạt động, vui lòng ping tôi trong phòng chat Battle of the Fellowships !

Chỉnh sửa: Tôi đã sửa một lỗi nhỏ trong chương trình của mình để điều chỉnh chuyển động của bot (chuyển tiếp | lùi) tùy thuộc vào nơi nó bắt đầu. Tôi quên làm điều này, vì vậy tôi đã chỉnh sửa nó ngay bây giờ.

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
đặt những kẻ hèn nhát của tôi để xấu hổ. làm tốt lắm
Moogie

4

Người trật bánh

Phải xóa nó hai lần vì tôi có một loạt lỗi logic. : P

Điều này chắc chắn có thể làm hỏng kế hoạch của bạn. ;)

Đội:

  • 1 nhân vật với Critical , Buff , StrongQuick để nhanh chóng hạ gục kẻ thù trong khi rất khó để đánh bại. +25 STR, +2 AGI, +3 INT
  • 1 nhân vật với Thông minh , Thông minh , Khôi phụcZap . Đặt phía sau là hỗ trợ và phục hồi sức khỏe của bất kỳ đồng đội nào đang thiếu HP, và có thể tấn công và tự vệ cần thiết. +14 STR, +3 AGI, +3 INT
  • 1 nhân vật với TrueSight , Spike , EvasiveWea . Không dễ để đánh, và nếu bạn làm thế, hoặc nếu bạn đến quá gần, nó sẽ thấy bạn và tấn công. +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()));
    }
}

Động lực nhóm thú vị! khiến tôi muốn thử một số hành động nhóm nâng cao hơn :)
Moogie

Cảm ơn. Tôi đã cố gắng kết hợp sức mạnh tổng hợp trong đội, nhưng nhân vật thứ ba có vẻ hơi lạc lõng. Có lẽ tôi sẽ cải thiện chiến lược này trong một bot trong tương lai.
TNT

Thật thú vị, thay thế ký tự thứ ba bằng một bản sao của ký tự đầu tiên cho kết quả tốt hơn nhiều.
TNT

4

Bắn tỉa

Một đội bắn tỉa bao gồm:

  • 1 Spotter (được trang bị thiết bị phát hiện tốt nhất hiện có, cho phép tổng quan về gần như toàn bộ bản đồ)
    • STR: 25; ĐẠI LÝ 5; INT: 5
    • Viễn thị , Viễn thị , Viễn thị , Viễn thị
  • 2 Shooters (được trang bị súng trường bắn tỉa đa mục tiêu mới nhất, nhược điểm duy nhất là tốc độ bắn chậm)
    • STR: 25; AGI: 5;INT: 5
    • Dệt , quan trọng , quan trọng , quan trọng

Bạn có thể sử dụng lại các ký tự đơn từ đây trong nhóm của mình, miễn là bạn thêm ít nhất một ký tự không có ở đây.

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, những tay súng bắn tỉa của bạn bắn tỉa những tay súng bắn tỉa hèn nhát của tôi :) làm tốt lắm
Moogie

3

Người sói

Tôi không phải là người giỏi nhất trong việc viết lựa chọn AI , đặc biệt là đối với một quy tắc phức tạp như cái này. Kết hợp với khả năng thấp để xem một trò chơi và quan sát các diễn viên đưa ra quyết định (và với kết quả hơi khác nhau giữa các lần chạy, có rất ít khả năng tính toán biên độ thành công của những thay đổi nhỏ để cải thiện logic AI), nhưng tôi đã có thể thực hiện một lựa chọn khả năng / thuộc tính vượt trội đã thống trị bộ bot hiện có.

Sử dụng Ranged , Swipe , StrongWerewolf và mặt khác sử dụng logic AI tương tự như LongSword , mặc dù có chút thay đổi.

Khó khăn để chọn các giá trị lý tưởng nhất, vì thậm chí không có thay đổi đôi khi có thể dẫn đến việc giảm từ "tốt nhất" xuống "tồi tệ nhất". Ngưỡng sức khỏe là 50 ở đây, nhưng dường như bất kỳ giá trị nào trong khoảng từ 10 đến 70 đều cho kết quả tương tự (không có bot nào khác đưa ra thử thách đủ cao để phân biệt đỉnh cao hiệu suất chính xác).

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;
        }
    }
}

Có một vài vấn đề (khai báo gói, cũng như không đặt tên tệp trên dòng đầu tiên) và tôi đã sửa chúng. Điều đó nói rằng, tôi không thể có được lớp tĩnh để vượt qua trình biên dịch của mình ... nhìn vào nó.
Nathan Merrill

Tôi đã hiểu ra: bạn đang bỏ lỡ một lần nhập:import fellowship.characters.EnemyCharacter;
Nathan Merrill

@NathanMerrill Tôi đã cố gắng kết hợp lớp thứ cấp thành một lớp bên trong bên ngoài Eclipse, đó có lẽ là cái đó.
Draco18

Đẹp! Bạn đã sử dụng các chức năng di chuyển của riêng tôi từ LongSword!
Kritixi Lithos

@KritixiLithos Yep, tôi đã gặp khó khăn khi viết phần "ai" của mọi thứ vì vậy tôi đã lấy một phần đơn giản chỉ để có một điểm khởi đầu và nó hoạt động rất tốt. Tôi đã cố gắng nghịch ngợm với họ để xem liệu tôi có thể làm điều đó tốt hơn không, vì họ sẽ tiếp tục tiến về phía trước nếu họ không nhìn thấy ai, ngay cả khi đối thủ của họ đứng sau họ, nhưng không làm được gì nhiều Sự khác biệt. Chủ yếu là vì cả những con sói và Kiếm sĩ Long đều không có khả năng chống lại sự tàng hình.
Draco18

2

Đường sắt

Bot này chỉ đơn giản là một phiên bản của Derailer đã có nhân vật thứ ba được thay thế bằng bản sao đầu tiên. Nó tạo ra kết quả tốt hơn nhiều so với Derailer.

Trong khi tạo Derailer, tôi muốn cung cấp cho mỗi nhân vật khả năng phối hợp tốt với nhau. Có một nhân vật có HP cao và sức tấn công và một nhân vật khác với hành động Khôi phục hoạt động độc đáo. Tuy nhiên, có vẻ như nhân vật thứ ba rất phù hợp. Đó có lẽ là lý do chính khiến Derailer không mang lại kết quả tốt. Vì vậy, tôi nghĩ rằng có một nhân vật thứ ba có thể làm việc tốt và được hưởng lợi từ những người khác sẽ là một ý tưởng tốt hơn.

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()));
    }
}

Kinh ngạc! Đây là cách khó khăn hơn Derailer
Kritixi Lithos

2

Noob/*Destroyer*/

Sử dụng Mạnh * 2, Tái tạoChoáng (Mục tiêu choáng trong 300 tích tắc tiếp theo)

TRẠM

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

AI

Hầu hết mã của Noob được lấy từ LongSword của tôi.

Chiến lược

Khi nhân vật lần đầu tiên nhìn thấy một nhân vật kẻ thù, ưu tiên hàng đầu là làm choáng kẻ thù, và sau đó cắt lát kẻ thù trong khi họ bị choáng. Và với sức khỏe và khả năng tái sinh cao, Noob sẽ có thể sống sót cho đến khi có thể sử dụng Stun một lần nữa.

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

Bức tường sống

Một bức tường gỗ sống có thể đi dọc chiến trường, tấn công mạnh vào bất kỳ kẻ thù nào đi qua và rút nhựa ra khỏi chúng để tăng sức khỏe tối đa. Hệ thống gốc của nó có thể phát hiện các rung động, cho phép nó tấn công ngay cả vào những kẻ thù vô hình. Nó bao gồm:

  • 2 Chi nhánh : STR 35, AGI 5, INT 5, Mạnh , Buff , Buff , Hấp thụ
  • 1 Root : STR 25, AGI , 5, INT , 5, True True , Buff , Buff , Hấp thụ

AI cực kỳ đơn giản: tìm kẻ thù gần đội nhất, sau đó toàn bộ bức tường tập trung vào kẻ thù duy nhất đó. Chỉ có một số phức tạp nhỏ: nếu không có kẻ thù trong tầm mắt, hãy đi về phía các góc ngẫu nhiên và / hoặc trung tâm của bản đồ (do đó cuối cùng sẽ săn lùng kẻ thù đang ẩn nấp); nếu kẻ thù nằm trong tầm tay, hãy tấn công nó ngay cả khi đó không phải là kẻ thù chúng ta đang nhắm mục tiêu (nhưng thích tập trung vào kẻ thù mà chúng ta đang nhắm tới, và thậm chí nhiều hơn kẻ thù chúng ta có thể OHKO).

Nhóm làm rất tốt; trong các mô phỏng, nhóm duy nhất (tồn tại tại thời điểm viết bài) có thể đánh bại nó là RogueSquad, và thậm chí sau đó không phải lúc nào (đôi khi ngay cả RogueSquad cũng chết vì sức mạnh của bức tường). Invulnerables đôi khi quản lý để rút ra một trận hòa.

Lý do cơ bản cho sự thành công của đội là do sự kết hợp của Buff × 2 và Hấp thụ; điều này có nghĩa là mỗi khi chúng ta tấn công kẻ thù chính của STR, chúng ta sẽ đạt được 40 HP trong thời gian ngắn (chỉ 10 HP trong thời gian dài do sự tái sinh gia tăng từ STR bị đánh cắp, nhưng sau đó cuộc chiến sẽ kết thúc và sự tái sinh tự nhiên của chúng ta sẽ giúp chúng ta vượt qua) và với tốc độ tái sinh tự nhiên là 12,5 hoặc 17,5, điều đó về cơ bản là không thể gây sát thương đủ nhanh để theo kịp quá trình tái sinh (một đội AGI có khả năng thực hiện bằng cách sử dụng hit- và chạy chiến thuật, nhưng chưa có ai xây dựng một trong số đó. { Cập nhật : Rõ ràng combo này không thực sự hoạt động (Hấp thụ chỉ tiêu hao 10 HP), nhưng dù sao thì đội vẫn chiến thắng.} Trong khi đó, nếu kẻ địch khôngSTR-sơ cấp, họ sẽ không thích thực hiện các đòn đánh 25 hoặc 35 lần lặp lại (và trên thực tế có thể hoàn toàn có thể được tập trung xuống trong một lượt của họ); và nếu kẻ địch là INT-sơ cấp và sử dụng các phép thuật để tự vệ (hi Invulnerables!), thì Earnb cuối cùng sẽ rút MP của chúng xuống đến mức chúng không còn đủ khả năng để sử dụng phép. (Ngoài ra, về cơ bản, chúng tôi không có gì phải sợ từ hầu hết các phép thuật; thời gian hồi chiêu của chúng quá dài để gây sát thương cho sự tái sinh của chúng tôi. Các ngoại lệ chính là Bẫy, không ai chạy được, và Poison, mất nhiều thời gian để mất đến 1000 hoặc 1400 HP, nhưng hoạt động được nếu Tường không đánh bại được người chơi trước.) True Sight vẫn là khả năng duy nhất thực tế có khả năng đánh bại kẻ thù vô hình (Track does '

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

DarkAbsorbers

Kẻ hấp thụ bóng tối là 2 anh em, chúng hấp thụ sinh lực của nạn nhân:

  • Oracle hấp thụ (có thể nhìn thấy kẻ thù vô hình)
    • STR: 25; AGI: 5; INT: 5
    • Truesight , linh hoạt , Ranged , Hấp
  • Hấp thụ nhanh (có thể hấp thụ nhanh hơn cả anh trai của mình)
    • STR: 25; AGI: 5; INT: 5
    • Nhanh chóng , linh hoạt , dao động , hấp thụ

Chúng luôn đi kèm với một đám mây bóng tối đang phát triển. Một khi nó đạt đến một khối lượng quan trọng, nó bắt đầu giết kẻ thù.

  • Đám mây bóng tối
    • STR: 5; AGI: 5; INT: 25
    • Bản sao , Zap , Bóng tối

Bạn có thể sử dụng lại các ký tự đơn từ đây trong nhóm của mình, miễn là bạn thêm ít nhất một ký tự không có ở đây.

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

"Bạn có thể chạy, nhưng bạn không thể ẩn ..." - LongSwordv2

Sử dụng Ranged , linh hoạt , nhanh , truesight

Bot này hoàn toàn giống với LongSwordv2, ngoại trừ việc nó sử dụng TrueSight thay vì Strong.

Thấy sự gia tăng của các bot vô hình, tôi quyết định tạo ra một bot tập trung vào việc đưa chúng ra ngoài vì chúng không thể bị phát hiện bởi nhiều bot. Với phạm vi dài và phạm vi Slice linh hoạt và gấp đôi Hành động cắt lát, LongSwordv2 có thể gây sát thương lớn trước khi các nhân vật của kẻ địch rơi vào phạm vi Cắt. Và trong giai đoạn thử nghiệm, tôi muốn nói rằng nó chiến thắng trước các đội tập trung vào các nhân vật vô hình hầu hết thời gian.

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;
    }
}

Tải xuống bot này không thành công, vì tiêu đề bị thiếu.
Sleafar

@Sleafar Chúng ta đi ... thêm nó!
Kritixi Lithos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.