การต่อสู้ของมิตรภาพ KotH


45

ในการท้าทายนี้คุณจะต้องสร้างมิตรภาพโดยมีเป้าหมายในการเอาชนะมิตรภาพอื่น ๆ ทั้งหมดในการต่อสู้

คบหา (ทีม) ประกอบด้วย 3 ตัวอักษร ตัวละครแต่ละตัวจะเคลื่อนไหวอย่างอิสระจากทีมที่เหลือ แต่พวกเขาจะต้องทำงานร่วมกันเมื่อต้องต่อสู้กับศัตรูของคุณ ทีมจะเผชิญหน้ากันในลักษณะหมุนหัว การชนะมีค่า 3 คะแนนความสัมพันธ์มีค่า 1 คะแนนและการสูญเสียมีค่า 0 คะแนน

ตัวละครมีความสามารถ การเลือกความสามารถของตัวละครของคุณคือส่วนที่สำคัญที่สุด (และสนุก) ใน KotHนี้ พวกเขาแข็งแกร่งและมีศักยภาพที่จะกำจัดศัตรูของคุณ

ตัวละครมีคะแนนสุขภาพ (HP) และเมื่อฮิต HP ของพวกเขา (หรือไปด้านล่าง) 0, พวกเขาตาย หากตัวละครทั้งหมดในทีมของฝ่ายตรงข้ามเสียชีวิตคุณก็จะชนะ!

ตัวละครมีมานา การกระทำส่วนใหญ่ต้องการมานาในการดำเนินการและหากคุณมีไม่เพียงพอการกระทำนั้นจะไม่สามารถใช้ได้สำหรับคุณ

ตัวละครมีความล่าช้าในการเปิด สิ่งนี้จะกำหนดจำนวนของการทำเครื่องหมายระหว่างแต่ละรอบ (เริ่มต้นที่ 100) ต่ำกว่าดีกว่า

ตัวละครมีแอตทริบิวต์ ตัวละครแต่ละตัวมีฐาน 5 ตัวในแต่ละคุณลักษณะและคุณจะได้รับ 20 คะแนนคุณลักษณะเพิ่มเติมเพื่อแบ่ง หลังจากกำหนดคะแนนแอตทริบิวต์แล้วแอตทริบิวต์หลักของคุณจะถูกตั้งค่าเป็นแอตทริบิวต์สูงสุดของคุณ

คุณลักษณะที่มีคือ:

  • ความแข็งแกร่ง: ให้ 10 Max HP และ. 5 HP ต่อเทิร์น
  • หน่วยสืบราชการลับ: ให้ 7 มานาสูงสุดและ. 1 มานาต่อเทิร์น
  • Agility: ลดการหน่วงเวลาการหมุน 1

การเคลื่อนไหว, วิสัยทัศน์, ช่วงของ
ช่วงมีดังนี้ (อยู่กึ่งกลางรอบ 0) บางช่วงมีความสำคัญซึ่งหมายความว่าพวกเขาสามารถขึ้นลงทางซ้ายขวาหรือลงเท่านั้น

    444
   43334
  4322234
 432111234
 432101234
 432111234
  4322234
   43334
    444

ตัวละครมีวิสัยทัศน์เริ่มต้นที่ 2 มีการแบ่งปันวิสัยทัศน์ระหว่างผู้เล่นที่มีมิตรภาพเดียวกัน

วิธีการเล่น


ผู้เล่นก่อสร้างจะสร้างมิตรภาพของพวกเขา คุณต้องทำตามขั้นตอนต่อไปนี้ :

  1. ให้แต่ละจุดแอตทริบิวต์ตัวอักษร ตัวละครแต่ละตัวเริ่มต้นด้วย 5 ในแต่ละสถานะพร้อมกับอีก 20 เพื่อกระจายระหว่าง 3

  2. ให้แต่ละความสามารถของตัวละคร อักขระแต่ละตัวเริ่มต้นด้วยสล็อตความสามารถ 4 ตัวและความสามารถจะใช้ 1 ช่องโดยค่าเริ่มต้น ความสามารถบางอย่างสามารถทำซ้ำได้และสามารถให้กับตัวละครหลายครั้ง ไม่อนุญาตให้ใช้ชุดความสามารถของการส่งอื่นโดยไม่ได้รับอนุญาตจากเจ้าของ

  3. เขียนรหัสสำหรับบอทของคุณ รหัสจะต้องเป็น Java และจะใช้สำหรับการต่อสู้ (ขั้นตอนต่อไป)

การปฏิบัติ

ตัวละครทุกตัวเริ่มต้นด้วยการกระทำมาตรฐาน 3 อย่าง:

  1. ขั้นตอน : ย้ายตัวละครของคุณในช่วงที่สำคัญ 1
  2. Slice : โจมตีศัตรูให้กับPrimaryAttributeในช่วงที่สำคัญ 1
  3. ยิ้ม : ไม่ทำอะไรเลย

ในเทิร์นของตัวละครจากนั้นจะต้องเลือกการกระทำที่จะดำเนินการ การดำเนินการอาจมีต้นทุนมานาและอาจมีคูลดาวน์ซึ่งกำหนดจำนวนรอบที่คุณต้องรอก่อนที่จะดำเนินการนั้นอีกครั้ง

ความสามารถ
ทุกตัวละครมี 4 ช่องความสามารถ หากความสามารถเป็นตัวเอียงมันเป็นการกระทำ

ความสามารถ

ชื่อคำอธิบายมานะคูลดาวน์ 
                                                     Mobility                                                     

Blink         ย้ายไปที่ตารางช่วง 4 2 2 
 Swap          สถาน Swap กับเป้าหมาย 5 5 
 Teleport      ย้ายได้ทุกที่ 20 5

Dash เพิ่มช่วงของขั้นทีละ 1 ทำซ้ำได้                                               
Mobile Step สามารถเคลื่อนที่ได้ใน 8 ทิศทาง                                                  
                                                    ทุ่ม                                                    

Quick         Slice สองครั้ง 3 0 
 Weave         Slice ศัตรูที่มองเห็นได้ทั้งหมด 15 ครั้ง 10

ดูดซับ Slice แต่ละอันจะขโมย 1 ในแอตทริบิวต์หลักของเป้าหมายของคุณ กินเวลา 20 รอบ                    
Cleave Each Slice สร้างความเสียหาย 1/2 กับศัตรูที่อยู่ใกล้เคียง                                           
Critital เพิ่มโอกาส 30% ที่ Slice จะสร้างความเสียหาย 200% ซึ่งทำซ้ำได้                               
Feast Each Slice จะเพิ่ม HP ของคุณ 3 ครั้งทำซ้ำได้                                             
Slice แบบยืดหยุ่นได้ใน 8 ทิศทางใด ๆ                                                      
มานาขโมย Slice ขโมย 2 มานา ซึ่งทำซ้ำได้                                                           
Reflexive Slice เมื่อหั่นบาง ๆ 0 3 
Ranged เพิ่ม 1 เข้ากับช่วงของ Slice                                                              
ปัดชิ้นที่ต่อเนื่องกันบนเป้าหมายเดียวกันสร้างความเสียหายได้มากกว่า 3 ครั้ง               
                                                    สถานะ                                                     

ปัดเป่า        ลบสถานะทั้งหมดออกจากเป้าหมาย ช่วงที่ 2 20 10 
 Duel          ตรึงคุณและเป้าหมายไว้จนกว่าคุณจะตาย ระยะ 1 25 0 
 ทำให้ล้มลง      คุณและเป้าหมายจะติดสตันในอีก 1,000 ติ๊ก 10 10 
 ดาวตก        ศัตรูทั้งหมดจะถูกสตัน 100 แต้มต่อ 25 25 10 
         เป้าหมายLeashถูกแช่แข็งสำหรับรอบถัดไปของพวกเขา 4 6 
 พิษ        พิษสำหรับ 1 HP เป็นเวลา 5 รอบ 5 0 
 ความเงียบ      เป้าหมายถูกปิดเสียงสำหรับ 5 รอบ 5 7 
 ช้า          เป้าหมายจะช้าลง 40 เห็บสำหรับ 3 รอบถัดไป 10 5 
 Stun          เป้าหมายจะถูกสตันสำหรับอีก 300 เห็บ 10 10

เย็นตัวละครอื่น ๆ ทั้งหมดในระยะ 2 จะถูกชะลอความเร็วโดย 10 เห็บ                                
ภูมิคุ้มกันไม่มีสถานะใดที่สามารถใช้ได้กับคุณ                                                           
                                                    เกี่ยวกับการป้องกัน                                                    

Force Field   Block ถัดไป 5 แหล่งของความเสียหาย ไม่ซ้อน 15 5 
 Ghost         เพื่อการเทิร์นความเสียหายทั้งหมดจะรักษา 10 10 
 Heal          Heal Target สำหรับ 20 HP 10 3 การ 
 คืนค่า       ทุกยูนิตจะถูกฟื้นฟูให้กลับสู่สภาพเต็มพลัง 20 40 
 Shield        คุณไม่สามารถถูกหั่นได้จนกว่าเทิร์นถัดไปของคุณ 3 0

หลบหลีกโอกาส 25% ที่ Slice จะไม่โจมตีคุณ ซึ่งทำซ้ำได้                                         
เสาสามารถหั่นได้เพียงครั้งเดียว                                                            
ฟื้นคืนชีพเมื่อถูกสังหารให้กลับมามีชีวิตด้วย HP เต็ม (และไม่มีสถานะ) 0 40 
Spikes เมื่อทำดาเมจได้รับความเสียหายครึ่งหนึ่งของดาเมจ                                           
                                                     วิสัยทัศน์                                                      

Cloak         Team จะมองไม่เห็นเป็นระยะ 5 ครั้ง 20 20 
 ซ่อน          คุณมองไม่เห็นเป็น 5 เทิร์น 4 7 
 เฟส         กลายเป็นล่องหนสำหรับ 1 เทิร์น 0 3 
 Track         Target ไม่สามารถล่องหนได้และรับความเสียหายมากขึ้น 10% ระยะเวลา 10 รอบ 5 5

ระยะการมองเห็นของศัตรูความมืดลดลง 1 Stacks แต่ไม่สามารถต่ำกว่า 1 ได้                                 
Far Sight Sight Range เพิ่มขึ้น 2 ทำซ้ำได้                                                    
Invisible คุณจะมองไม่เห็นถ้าคุณเริ่มมองเห็นศัตรู                               
True Sight เผยยูนิตที่ซ่อนอยู่ทั้งหมดในระยะที่ 2 เมื่อถึงจุดเริ่มต้น                                     
                                                     ความเสียหาย                                                      

Drain         สร้างความเสียหาย 5 เป้าหมายแก่เป้าหมายและรักษาตัวเอง 5 HP ในขณะที่อยู่ในระยะ 1 10 10 
 Lightning     สร้างความเสียหาย 15 เป้าหมายแก่ศัตรูทั้งหมด 20 10 
 K / O           สังหารเป้าหมายหากเป้าหมายต่ำกว่า 20% HP 20 0 
 กับดักกับดัก          ที่มองไม่เห็น กับดักจะสร้างความเสียหาย 15 ดาเมจเมื่อเหยียบ สแต็ค 10 2 
 Zap           สร้างความเสียหาย 30 ต่อเป้าหมาย 30 5

สร้างความเสียหาย 5 ดาเมจทุกเทิร์นให้กับศัตรูทั้งหมดภายในระยะ 1 ซึ่งทำซ้ำได้                       
                                                      สถิติ                                                      

Werewolf      เพิ่ม 10 ในสถิติทั้งหมดเป็นเวลา 5 รอบ 30 25

บัฟเพิ่มพูล HP ของคุณเป็นสองเท่า ซึ่งทำซ้ำได้                                                           
การกระทำที่ฉลาดมีคูลดาวน์ที่สั้นลง 20% ซึ่งทำซ้ำได้                                             
เน้นเพิ่มอัตราการมานาของคุณเป็น Int / 10 ซึ่งทำซ้ำได้                                  
Regenerate เพิ่มอัตรา Regneration ของคุณด้วย Strength / 2 ซึ่งทำซ้ำได้                                 
การกระทำที่ชาญฉลาดนั้นใช้มานาน้อยกว่า 2 ซึ่งทำซ้ำได้                                                      
แข็งแรงคุณจะได้รับ 10 คะแนนคุณลักษณะ ซึ่งทำซ้ำได้                                                  
อ่อนแอคุณเสีย 15 คะแนนคุณลักษณะ คุณได้รับความสามารถ 2 ช่อง (สิ่งนี้จะเป็นหนึ่งในนั้น)                  
                                                      อื่น ๆ                                                      

แบร์          สามารถเรียกหมีที่มี 5 ในแต่ละสถิติ 8 10 
 โคลน         โคลนด้วยตัวคุณเอง ใช้ความสามารถสองช่อง 100 100 
 Steal         แทนที่การกระทำนี้ด้วยการกระทำครั้งสุดท้ายที่ศัตรูใช้เป้าหมาย ใช้งานได้นาน 10 รอบ 5 0 
 กำแพง          สร้างกำแพงที่ไม่สามารถใช้ได้บนพื้นที่ว่างเปล่าที่ถูกกำหนดเป้าหมายในช่วง 6 10 10 

สถานะ:

  • งันช่วยให้ตัวละครของคุณไปเพียงดำเนินการด้วยรอยยิ้มและกินเวลา X เห็บ
  • ตรึงป้องกันตัวละครของคุณจากการเคลื่อนไหวและเป็นเวลา X เปลี่ยน
  • ความเงียบช่วยป้องกันตัวละครของคุณไม่ให้ทำอะไรเลยนอกจากรอยยิ้มก้าวหรือสไลซ์และเปลี่ยนเป็น X
  • พิษสร้างความเสียหายให้กับตัวละครของคุณสำหรับความเสียหาย X สำหรับ Y เทิร์น หากคุณใช้พิษอื่นความเสียหายจะรวมเข้าด้วยกันและระยะเวลาจะถูกรีเฟรช
  • Slow เพิ่ม X เข้ากับจำนวนของการทำเครื่องหมายระหว่างรอบของคุณ มันจะไม่ส่งผลต่อเทิร์นที่กำลังจะมาถึงของคุณหลังจากนั้น
  • สิ่งที่มองไม่เห็นนั้นทำให้คุณไม่สามารถมองเห็นหรือเสียหายจากคู่ต่อสู้ได้ หากคุณดำเนินการใด ๆ นอกเหนือจากขั้นตอนหรือรอยยิ้มการดำเนินการนั้นจะถูกลบออก หากฝ่ายตรงข้ามมีความสามารถในการมองเห็นของคุณพวกเขาจะหายตัวไป

สถานะทั้งหมด (ยกเว้นพิษ) ทำหน้าที่เป็นอิสระจากกัน

หมายเหตุด้านข้าง:

  • หากมีเน็คไทสำหรับแอตทริบิวต์หลักมันจะถูกแก้ไขเป็น STR> AGI> INT
  • คุณเล่นบนตาราง 10x10 ทีมจะถูกวางในฝั่งตรงข้าม
  • เปอร์เซ็นต์สแต็กคูณยกเว้น Clever

กฎการส่ง

คุณต้องใช้งาน 2 ฟังก์ชั่น:

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

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

นอกจากนี้คุณยังสามารถเข้าถึงตัวแปรสามตัว (ตัวแปรสมาชิก):

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

แค่นั้นแหละ. ด้านล่างนี้คุณสามารถค้นหา API ที่สมบูรณ์ตามลำดับตัวอักษร:

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

ข้างต้นคือฟังก์ชั่นทั้งหมดที่คุณอาจต้องการสำหรับการส่งของคุณ ไม่อนุญาตการสะท้อนกลับ หากการส่งไม่ถูกต้องไม่ว่าด้วยเหตุผลใดก็ตามโปรดลบออกหรือเพิ่ม "ไม่ถูกต้อง" ในส่วนหัว การส่งของคุณไม่ควรมีการประกาศแพ็คเกจ การส่งของคุณควรอยู่ในบล็อกรหัสหลายบรรทัดแรกและบรรทัดแรกจะต้องมีชื่อไฟล์

วิธีเรียกใช้โครงการ:

มีหลายวิธี:

  1. ดาวน์โหลดไฟล์ JARjava -jar Fellowship.jarและเรียกใช้ หากคุณต้องการดาวน์โหลดส่งอื่น ๆ -q 99744ผ่าน java ต้องชี้ไปที่ JDK ไม่ใช่ JRE
  2. โคลนrepo คอมไพล์gradle runและวิ่ง คุณต้องมีการติดตั้ง gradle และหากคุณต้องการส่งอาร์กิวเมนต์ให้ใช้-PappArgs="['arg1', 'args2']"
  3. โคลนrepo คอมไพล์และรวบรวมด้วยตัวคุณเอง คุณจะต้องห้องสมุดต่อไปนี้: 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

หากคุณโคลนคุณต้องใช้การ--recursiveตั้งค่าสถานะและเมื่อคุณดึงการอัปเดตให้รวม--recurse-submodulesสำหรับข้อใดข้อหนึ่งข้างต้นคลาสของคุณต้องไปที่submissions/javaโฟลเดอร์ ถ้าคุณกำลังใช้ gradle หรือคอมไพล์ด้วยตัวคุณเองคุณสามารถใส่คลาสลงในโครงงานได้ คุณจะต้องไม่แสดงข้อคิดเห็นบางบรรทัดในฟังก์ชั่นหลักและอัปเดตพวกเขาให้ชี้ไปที่ชั้นเรียนของคุณ

สกอร์:

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

หากคุณมีคำถามใด ๆ หรือต้องการความช่วยเหลือแสดงความคิดเห็นด้านล่างหรือเข้าร่วมห้องสนทนา ! ขอให้โชคดีและสนุก


1
ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
เดนนิส

สายฟ้าถูกระบุว่าเป็นDeal 15 damage to all enemiesแต่ศัตรูที่มองไม่เห็นนั้นไม่ได้รับผลกระทบจากสายฟ้า นี่เป็นข้อบกพร่องหรือไม่? มิฉะนั้นการล่องหนดูเหมือนว่าจะแข็งแกร่งสำหรับฉัน ...
CommonGuy

1
ความท้าทายนี้มีความซับซ้อนกว่าความท้าทายก่อนหน้านี้เป็นพิเศษ ฉันหวังว่าจะมีรูปแบบที่นี่ซึ่งทำให้บางสิ่งบางอย่างเช่นนี้แข่งขันได้มากขึ้นในระยะยาว
Sparr

2
ใช่ทราบว่าตัวเลือก -g แต่เมื่อฉันพัฒนาบอทของฉันฉันมีมันยังไม่อยู่ในสถานะที่ใช้งานได้ดังนั้นฉันเริ่มทำทางเลือก มันเป็นพื้นฐานในขณะนี้ แต่มันมีรัศมีสายตาที่มองเห็นได้ นี่คือการจับผู้เล่น Bear Cavalry vs Template! การจับกุม
Moogie

1
คุณสามารถทดสอบตัวละครใหม่และอัปเดตคะแนนได้หรือไม่
เลมอนที่ทำลายได้

คำตอบ:


10

StaticCloud

คลาวด์ที่เติบโตอย่างรวดเร็วซึ่งสร้างความเสียหายแบบคงที่ให้กับทุกคนที่เข้ามาใกล้ มันประกอบด้วย:

  • 1/3 ส่วนที่มองไม่เห็น
    • STR: 5; AGI: 5; INT: 25
    • โคลน , ที่มองไม่เห็น , แบบคงที่
  • 2/3 ส่วนที่มองเห็นได้
    • STR: 5; AGI: 5; INT: 25
    • โคลน , คงที่ , คงที่

คุณสามารถใช้อักขระเดี่ยวซ้ำได้จากที่นี่ในทีมของคุณตราบใดที่คุณเพิ่มอักขระอย่างน้อยหนึ่งตัวที่ไม่ปรากฏที่นี่

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
อันนี้น่าสนใจเพราะคงที่ไม่ทำลายการล่องหน
Draco18s

7

เทมเพลตเพลเยอร์

ใช้ช่วงระยะเวลา , มีความยืดหยุ่น , ZapและKO คุณได้รับอนุญาตให้ใช้ความสามารถนี้หากคุณต้องการ

อย่าลังเลที่จะใช้บอทนี้เป็นเทมเพลตสำหรับสร้างของคุณเอง

โปรดจำไว้ว่าคุณต้องเปลี่ยนชื่อไฟล์ในบรรทัดแรกเช่นเดียวกับการเลือกชุดความสามารถของคุณเอง

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

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

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

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

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

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

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

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

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

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

7

CowardlySniperMk2

ใช้Zap , Farsight * 2 และซ่อน

บอทนี้เป็นคนขี้ขลาด ความสำคัญสูงสุดของมันคือไม่ควรถูกกำหนดเป้าหมาย ด้วยเหตุนี้เขาจึงใช้สายตาที่เหนือกว่าเพื่อดูว่าศัตรูอยู่ที่ไหน มันใช้ความรู้นี้เพื่อหลีกเลี่ยงการถูกมองขณะติดตาม / ติดตามศัตรูโดยไม่ถูกมองเห็น หากเห็นหรือมองเห็นได้ในเทิร์นถัดไปบอทจะ 'ซ่อนตัว' ไม่ปรากฏตัวในช่วงเวลาหนึ่ง

ในโหมดติดตามและมีมานาและคูลดาวน์เพียงพอแล้วจะ 'Zap' ศัตรูที่อ่อนแอที่สุดที่มองเห็นได้

เมื่อมานาลดเหลือ 10% จากนั้นจะหลบหลีกศัตรูจนกว่าจะฟื้นฟูมานา ด้วยวิธีนี้มันสามารถปะทะกับศัตรูที่ติดตามได้เร็วที่สุด หวังว่าจะลบล้าง HP ที่ฟื้นฟูศัตรูได้

หมายเหตุเนื่องจาก 'Zap' เป็นช่วงที่ไม่มีที่สิ้นสุดสมาชิกในทีมจะกำหนดเป้าหมาย bot เดียวกันทั้งหมดเมื่อทำการจับคู่

ฉันมีความแตกต่างอื่น ๆ ของความคิดพื้นฐานเดียวกันนี้ที่ฉันอาจเพิ่มเป็นคำตอบ: พวกเขาทั้งหมดมีประโยชน์ / detriments ที่แตกต่างกันที่ถูกเอาเปรียบ / เปิดเผยขึ้นอยู่กับคู่ต่อสู้ที่มีอยู่

CowardlySniperMk2.java

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

public class CowardlySniperMk2 extends Player{

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

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

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

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

        rechargingManaMap.put(character,rechargingMana);

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

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

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

        for (ReadonlyAction action: actions){

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

                case "Step":

                    Point2D chosenLocation = null;

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

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

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

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

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

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

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

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

                    action.setLocation(chosenLocation);
                    break;

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

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

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

                    break;

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

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

        }


        int highestPriority = -1;
        ReadonlyAction chosen = null;

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

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

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

ชื่อไฟล์ควรเป็น CowardlySniperMk2 :)
Nathan Merrill

oops: การตัดและแปะทำให้ฉันดูเหมือนคนโง่!
Moogie

แจ้งเตือนฉันเกี่ยวกับ B Dasher ของ MarioKart Wii MK2 :)
Kritixi Lithos

@KritixiLithos เตือนฉันถึง Bamboozler 14 Mk II ของ Splatoon ;)
ทีเอ็นที

7

TheWalkingDead

ซอมบี้ทุกคนรู้จักพวกเขา พวกเขาอยู่ในกลุ่มโดยไม่ทำอะไรเลยจนกว่าจะมีคนมาปรากฏตัว มันยากที่จะฆ่าและไม่ว่าคุณจะฆ่ามากแค่ไหนก็ตาม และพวกมันมักปรากฏออกมาจากด้านหลังของคุณ

  • 1 x ซอมบี้ # 1 (แข็งแกร่งที่สุดและดังนั้นจึงเป็นอัลฟาซอมบี้)
    • STR: 25; AGI: 5; INT: 15
    • โคลน , ฟื้นคืนชีพ , แข็งแรง
  • 2 x Zombie # 2 (ไม่มีใครอยากเป็น Zombie # 3 ในเครดิตการปิดดังนั้นทั้งคู่จึงมีหมายเลขเดียวกัน)
    • STR: 15; AGI: 5; INT: 15
    • โคลน , ชุบชีวิต , ดูดซับ

คุณสามารถใช้อักขระเดี่ยวซ้ำได้จากที่นี่ในทีมของคุณตราบใดที่คุณเพิ่มอักขระอย่างน้อยหนึ่งตัวที่ไม่ปรากฏที่นี่

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

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

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

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

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

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

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

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

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

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

ฉันไม่สามารถให้บ็อตของคุณทำงาน ... ชั้นฐาน Sleafar นำเข้ามิตรภาพตัวอักษรตัวละครอินเทอร์เฟซที่ดูเหมือนจะไม่อยู่ในสาขาหลักล่าสุด ...
Moogie

@Moogie นาธานได้รับการแก้ไขปัญหาที่เกิดขึ้น
Sleafar

7

The Invulnerables

ทีมนักรบที่แข็งแกร่งที่สามารถอยู่รอดได้เกือบทุกอย่าง สิ่งนี้นำไปสู่การหมดเวลามากซึ่งน่าเสียดายที่ฉันมักจะไม่ชนะ อย่างไรก็ตามไม่มีมิตรภาพที่ฉันได้เห็นไม่สามารถเอาชนะได้และเมื่อทีมแพ้มันบ่อยครั้งที่ตัวละครยังคงอยู่รอด

การจับคู่ที่ยากที่สุดของทีมนี้คือ Bear Cavalry (พวกเขาไม่สามารถเช็ดทีมนี้ได้ แต่โดยปกติแล้วจะชนะในการเบรคเนื่องจากหมายเลขที่แท้จริง) ทีม Rogue (ทีมค่อนข้างอ่อนแอต่อพิษ); และแวมไพร์ (ฉันยังไม่แน่ใจว่าทำไม)

ในการจำลองทีมมักจะมาก่อนเสมอ คะแนนของมันค่อนข้างคงที่ ผู้ชนะตามปกติจะขึ้นอยู่กับว่า Rogue Squad ทำกับคู่แข่งคนอื่นได้ดีแค่ไหน

Mage ใน Noxious Sphere

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

Mage ปกป้องตัวเองด้วยการใช้คอมโบของPillarและForce Field ; เมื่อได้รับการโฟกัสและมีสติปัญญาสูงมันจะฟื้นฟูมานาให้มากพอที่จะใช้สกิล Force Field ในการคูลดาวน์ในแต่ละครั้ง สมมติว่าฝ่ายตรงข้ามไม่มีความว่องไวที่เพิ่มขึ้นพวกเขาสามารถโจมตีได้มากถึงห้าสไลซ์ต่อห้าตาและแหล่งความเสียหายห้าแหล่งจะถูกปิดกั้นในช่วงเวลานั้น กล่าวอีกนัยหนึ่งนักเวทย์ต้องการคาถาเพื่อเอาชนะอย่างแน่นอน การหั่นไม่ได้ผลด้วยตัวเองไม่ว่าคุณจะเก่งแค่ไหนก็ตาม

ผู้วิเศษสามารถโจมตีผ่านการหั่นเป็นเวลา 25 ในกรณีฉุกเฉิน แต่ส่วนใหญ่จะร่ายPoisonซ้ำ ๆ ซึ่งสามารถส่งผลให้ MP ฟื้นฟูได้ในระดับสูง เนื่องจาก Poison มีระยะอนันต์ จำกัด ด้วยวิสัยทัศน์ของทีม Mage จึงเป็นวิธีที่ทีมนี้จะโจมตีศัตรูที่หนักหน่วงและหนักหน่วงมาก ส่วนที่เหลือของทีมรักษาวิสัยทัศน์ไว้ในขณะที่ Mage สร้างความเสียหายเป็นสองเท่า พิษย่อมจบลงเร็วกว่าการฟื้นฟู HP ในที่สุดและฉันไม่ต้องกังวลเกี่ยวกับความเสียหายแบบคงที่เช่น Spikes ในกระบวนการ

The Giant Clifftop Falconer

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

หน้าที่หลักของ Falconer คือการทำให้ส่วนที่เหลือของวิสัยทัศน์เป้าหมายของทีมเป็นไปได้เพื่อที่พวกเขาจะสามารถถูกโจมตีโดยการสอดแนมแผนที่ด้วยสายตาของนกเหยี่ยว Far Sightให้การมองเห็นที่เพียงพอว่าศัตรูที่พยายามซ่อนหรือวิ่งหนีสามารถตามปกติด้วยระยะการมองเห็นที่มุมหนึ่งของแผนที่ มันไม่ง่ายเลยที่จะให้ Falconer หลบหนี True Sightคือการไล่ล่าหลักของทีมต่อศัตรูที่มองไม่เห็นซึ่งไม่สามารถสร้างความเสียหายได้ มีขนาดใหญ่และแข็งแรงทำให้ Falconer นั้นทนทานต่อความเสียหายและสามารถแบ่งส่วนได้มากเมื่อจำเป็น ในที่สุด Falconer สามารถปล่อยเหยี่ยวลงบนฝูงชนของศัตรูด้านล่างสั่งให้พวกเขาไปสานในหมู่ศัตรูและสร้างความเสียหายขนาดใหญ่ (35) พื้นที่

นอกเหนือจากงานเหยี่ยวของล่าสัตว์ศัตรูและการรักษาวิสัยทัศน์ของศัตรูเพื่อให้ผู้วิเศษสามารถทำให้เป็นพิษพวกเขาความสามารถในการสาน 35 เป็นกุญแจสำคัญในการจัดการกับศัตรูกลุ่มทีม; เป็นไปได้ที่จะยิงศัตรูจำนวนมากในแบบนั้นและปล่อยให้พวกเขาอยู่ในระดับต่ำพอที่จะทำให้ทีมที่เหลือเสร็จ การจับกลุ่มเป็นกลยุทธ์การสู้ที่ค่อนข้างมากภายใต้กฎเหล่านี้และการสานเป็นหนึ่งในไม่กี่ตัวนับจริงที่มี ถึงอย่างนั้นมันก็ยังไม่ดีพอสำหรับตัวมันเอง

Trollbone Skullrender

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

ภารกิจของ Trollbone คือการเก็บพยุหะศัตรูไว้ในขณะที่หน่วยอื่นสามารถทำงานได้ เช่นเดียวกับผู้วิเศษ Trollbone มีคาถาอนันต์ในช่วงที่น่าพิศวง คอมโบนี้ทำได้ดีมากกับพิษของผู้วิเศษ หากเป็นไปได้ที่จะเผชิญหน้ากับศัตรูทีละคน (และกับหลาย ๆ ทีมมัน) เหยี่ยวจะได้รับการมองเห็น Trollbone จะทำให้ตกใจพวกเขาแล้วผู้วิเศษจะวางยาพิษกับพวกเขาและพวกเขาจะตาย โดยไม่มีความสามารถในการทำสิ่งใด (สิ่งที่น่าพิศวงเป็นเวลา 1,000 ติ๊กบนเป้าหมายและ Trollbone จะสร้างคูลดาวน์ใหม่ให้เร็วขึ้นกว่านั้นเล็กน้อย) นอกจากนี้ยังเป็นสิ่งที่ดีในการปกป้อง Trollbone จากศัตรูที่แข็งแกร่งเดี่ยว พวกเขาไม่สามารถทำอะไรกับเขาได้หากพวกเขาไม่รู้สึกตัว แน่นอนการทุบกะโหลกกับศัตรูนั้นมีแนวโน้มที่จะทำให้ทั้งคู่สั่นสะเทือนทำให้ตกใจและพิษ (และสถานะอื่น ๆ ที่ไม่มีใครใส่ใจ) ในฐานะตัวละครที่เน้นคาถาที่ยังไม่ได้มีความโน้มเอียงอย่างน่าอัศจรรย์ Trollbone จะสร้างเวทย์มนตร์ใหม่โดยไม่ต้องใช้สติปัญญา แต่ผ่านการดื่มเลือดของศัตรูของเขาทำMana Stealทุกการโจมตี นี่เป็นอัตราการฟื้นฟู MP ที่ค่อนข้างดี (และศัตรูที่ติดสตันทำให้เป้าหมายง่ายต่อการขโมย MP จาก) ในที่สุด Trollbone ก็เกิดอาละวาดเป็นครั้งคราวและจะสานผ่านกลุ่มศัตรูในขณะที่หัวของพวกเขากระแทกและดื่มเลือดของพวกเขา กับฝูงศัตรูที่มีขนาดใหญ่พอจริง ๆ แล้วมันจะฟื้นมานาและมันสามารถจบฝูงที่ Falconer อ่อนแอ (25 + 35 คือ 60 ดังนั้นมันจึงใช้ได้แม้ว่าศัตรูจะสร้างใหม่ในระดับหนึ่ง)

กลยุทธ์

แตกต่างจากหลาย ๆ ทีมฉันให้ความสำคัญกับ AI มากกว่าการสร้างทีม กฎพื้นฐานข้อหนึ่งคือทีมจะพยายามจัดกลุ่มหากพวกเขาไม่ยุ่งทำอย่างอื่นทำให้ยากขึ้นสำหรับพวกเขาที่จะได้รับการล้อมรอบและสามารถปกป้องซึ่งกันและกัน หากพวกเขาถูกจับกลุ่มพวกเขาจะพยายามซ่อนตัวอยู่ในมุมหนึ่ง ในทางกลับกันหากศัตรูพยายามหลบหนีหรือวิ่งหนีพวกเขาท่องไปในแผนที่หยิบและเดินไปยังมุมสุ่มหรือศูนย์กลาง นี้มากหรือน้อยรับรองว่า Falconer จะจุดเป้าหมายในที่สุด การเคลื่อนไหวถูกออกแบบมาเพื่อไม่ให้ศัตรูได้รับการโจมตีครั้งแรกถ้าเป็นไปได้ ศัตรูจะต้องเดินเข้าไปใน Slice เล็งตนเอง ผู้วิเศษจะเสมอปล่อย MP ไว้สำหรับสนามพลังป้องกันการสูญเสีย MP ที่อ่อนล้า (วิธีเดียวที่สิ่งนี้สามารถล้มเหลวคือด้วย Absorb ซึ่งสามารถผ่านสนามพลังได้แม้ว่าความเสียหายจะไม่เกิดขึ้นก็ตาม) นี่ไม่ใช่ปัญหาปกติ โดยปกติแล้วผู้วิเศษสามารถสแปมพิษทุกคราวโดยไม่มีปัญหา หากไม่ถูกรบกวนทีมชอบที่จะไล่ล่าศัตรูทีละคนตะลึงพวกเขาเมื่อพวกเขาเข้ามาในสายตาจากนั้นวางยาพิษซ้ำ ๆ จนกว่าพวกเขาจะตาย เมื่อมีศัตรูคนอื่น ๆ ทีมจะพยายามว่าวพวกเขาหากเป็นไปได้วิ่งวนเป็นวงกลมและบังคับให้ศัตรูส่วนใหญ่ไล่ล่าในขณะที่น่าทึ่งและพิษหนึ่งในนั้น ปัญหาหลักคือฝูงซึ่งเป็นสาเหตุที่มีการทอผ้ามากที่นี่ แต่ก็ดูเหมือนยากที่จะเอาชนะกลยุทธ์

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

มีประสิทธิภาพมาก :) สวยงามน่าทึ่งเหมือนกลยุทธ์ที่มีประสิทธิภาพ!
Moogie

Noobs ของฉันไม่สามารถเอาชนะตัวละครของคุณได้แม้ว่ามันจะมีสุขภาพแค่ 50 เท่านั้น!
Kritixi Lithos

แก้ไขข้อผิดพลาดที่ @Sleafar ชี้ให้เห็น

ฉันคิดว่าคุณมีจุดทักษะจำนวนมากเกินไปใน falconer และ trollbone ของคุณ
Eumel

แก้ไขด้วย มันเป็นเพียงการพิมพ์ผิดในคำอธิบายรหัสถูกต้อง

7

RogueSquad

ทีมโกงประกอบด้วย:

  • 1 ลูกเสือ (อยู่ในเงามืดขณะสำรวจแผนที่)
    • STR: 5; AGI: 5; INT: 25
    • โคลน , ที่มองไม่เห็น , ห่างไกลสายตา
  • 2 Assasins (โจมตีศัตรูด้วยพิษร้ายแรง)
    • STR: 5; AGI: 5; INT: 25
    • โคลน , Poison , มุ่งเน้น

พลังที่ยิ่งใหญ่ที่สุดที่ทั้งสองสามารถใช้ได้คือการเรียกสมาชิกเพิ่มเติมของทีมเพื่อสนับสนุนพวกเขา

คุณสามารถใช้อักขระเดี่ยวซ้ำได้จากที่นี่ในทีมของคุณตราบใดที่คุณเพิ่มอักขระอย่างน้อยหนึ่งตัวที่ไม่ปรากฏที่นี่

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

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

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

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

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

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

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

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

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

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

คลาสฐานสำหรับบอททั้งหมดของฉัน

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

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

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

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

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

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

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

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

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

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

    protected abstract class Character {
        protected final ReadonlyCharacter delegate;

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

        protected abstract ReadonlyAction choose();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    protected abstract Character createCharacter(ReadonlyCharacter delegate);

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

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

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

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

ทำได้ดีมาก ทีมที่แข็งแกร่งในการเอาชนะ ... ยอมรับการท้าทาย: P
Moogie

6

แทตย์

ฉันใหม่สำหรับสิ่งนี้และฉันไม่แน่ใจว่าฉันรู้ว่าฉันกำลังทำอะไรอยู่ แต่ฉันคิดว่ามันน่าสนใจดังนั้นนี่คือความพยายามของฉัน

แวมไพร์จะค้นหาศัตรูและกำหนดเป้าหมายที่อ่อนแอที่สุดระบายชีวิตจากพวกเขาในขณะที่เติบโตแข็งแกร่งขึ้นและฟื้นสุขภาพของตนเองพร้อมที่จะไปหาเหยื่อรายต่อไป หากพวกเขาได้รับบาดเจ็บอย่างมีนัยสำคัญพวกเขาจะพยายามที่จะย้ายออกไปจนกว่าการฟื้นฟูตามธรรมชาติของพวกเขาคืนพวกเขาไปสู่สภาพการต่อสู้

ใช้ดูดซับ , งานเลี้ยง , Regenerate , ที่แข็งแกร่งกับทุกอย่างใน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;
    }
}

ฉันชอบการใช้การกระทำที่แฝงอยู่ทั้งหมด! ทำได้ดีนี่.
Moogie

6

ทหารม้าหมี

ใช้ดูดซับ , โคลนและหมี ; สถิติมี(9, 0, 11)

ในเทิร์นแรกทุกคนสร้างโคลนของตัวเองเพื่อให้ทีมมี 6 ตัวในสนาม จากนั้นพวกเขาก็จู่โจมศัตรูส่งสแปมเมื่อใดก็ตามที่ทำได้และทำให้ศัตรูอ่อนแอลงด้วยการโจมตีที่น่าดึงดูด

รหัสเป็นระเบียบ แต่ดูเหมือนว่าจะทำงาน ฉันคัดลอกบางส่วนจาก Template Player

คุณสามารถใช้ตัวละครของทีมนี้ในแบบที่คุณชอบ

BearCavalry.java

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

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

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

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

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

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

หมีของคุณเป็นกลยุทธ์ที่มีประสิทธิภาพต่อมือปืนขี้ขลาดของฉัน :) มีเป้าหมายมากเกินไปสำหรับบอทของฉันที่จะเข้าสู่สถานะสแตนด์อะโลนเพื่อเริ่มต้น zapping! ทำได้ดีมาก
Moogie

5

แหลมคม

Spiky คือชื่อของเขาหมายถึงไม่ถูกโจมตีอย่างสุ่มสี่สุ่มห้า เขาเป็นคนที่มีพลังสามารถสร้าง HP จำนวนมากได้อีกครั้งและโดนรถบรรทุก เขาจะโฮเวอร์ที่กึ่งกลางของแผนที่รอให้ใครบางคนเข้ามาใกล้

ใช้Strong (STR +10) x2, Regenerate , Spikesและไปที่ STR เต็ม (+40, 0, 0)

Spiky.java

import fellowship.*;

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

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

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

public class Spiky extends Player {

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

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

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

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

        return chosen;
    }

}

> การส่งของคุณไม่ควรมีการประกาศแพ็คเกจ การส่งของคุณควรอยู่ในบล็อกรหัสหลายบรรทัดแรกและบรรทัดแรกจะต้องมีชื่อไฟล์
Kritixi Lithos

ฉันเพิ่งระบุสิ่งที่พูดในโพสต์
Kritixi Lithos

@KritixiLithos เดาว่าเป็นสิ่งเดียวที่ฉันทำถูก ขอบคุณ
Thrax

เยี่ยมบอท! Spiky กำจัดบอทที่ดีที่สุดของฉัน
Kritixi Lithos

คุณเปลี่ยนThreadLocalRandom.current()เป็นgetRandom()เหรอ ช่วยให้เกมสามารถกำหนดได้
Nathan Merrill

5

หมอผี

โคลนนิ่งตัวเองเพื่อสร้างความเสียหายทันทีแก่ศัตรูทุกตัวมากที่สุดเท่าที่จะเป็นไปได้ด้วยWeave (ก่อนหน้านี้มันเป็นสายฟ้าแลบ แต่Weaveจะสร้างความเสียหายได้มากขึ้น

Sorcerer.java

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

public class Sorcerer extends Player {

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

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

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

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

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

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

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

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

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

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

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

การโคลนนิ่งดูเหมือนจะเป็นทางเลือกยอดนิยมสำหรับบอท ... ฉันควรใช้บอทของคุณเป็นแรงบันดาลใจ
Moogie

4

Longsword

ใช้ช่วง (เพิ่ม 1 ถึงช่วงของ Slice), ยืดหยุ่น (Can Slice ใน 8 ทิศทาง), ด่วน (Slice สองครั้ง, มานา: 3, คูลดาวน์: 0), แข็งแกร่ง (คุณได้รับ 10 คะแนนคุณลักษณะมากขึ้น)

สถิติ

จุดเริ่มต้นที่ 5 คือฐาน

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

ก่อนอื่นฉันสนุกกับการทำบอทนี้และฉันชอบ KotH นี้ (นี่เป็นครั้งแรกที่ฉันได้รับการท้าทาย KotH!) (ฉันอาจโพสต์บอทมากขึ้น)

ธ ปท

บอทนี้อาศัยความสามารถในการโจมตีเพื่อเอาชนะคู่ต่อสู้ เท่าที่ฉันทดสอบบอทนี้ดีต่อบอทที่มีสุขภาพค่อนข้างต่ำ นอกจากนี้มันยังมีระยะการโจมตีที่กว้างและสามารถเล็งเป้าหมายไปยังศัตรูส่วนใหญ่ (หรือครึ่ง) ได้อย่างง่ายดาย

เพื่อเปรียบเทียบบ็อตนี้กับบทบาท NetHack ฉันจะบอกว่ามันคล้ายกับคิรีเนื่องจากแนวคิดของ "LongSword" และสุขภาพโดยเฉลี่ย

ชื่อ

บอทนี้มีระยะที่ยาวกว่าบอทเล็กน้อยและสามารถโจมตีได้ทุกทิศทาง สิ่งนี้ทำให้ฉันนึกถึงดาบยาวส่วนใหญ่ใน NetHack ดังนั้นฉันจึงตั้งชื่อบอทของฉันให้เป็นเช่นนั้น

พฤติกรรม

หากตัวละครไม่สามารถมองเห็นตัวละครศัตรูก็จะไปทางด้านตรงข้ามของสนาม (พื้นที่วางไข่ของศัตรู / "ฐาน" ของศัตรู) เพื่อค้นหาตัวละครของศัตรู หากพบศัตรูก็จะโจมตีพวกเขาด้วย Quick, Slice (ลดระดับความสำคัญ) หากไม่สามารถกำหนดเป้าหมายศัตรูบอทจะไปยังตัวละครศัตรูเพื่อทำลายพวกมัน

หากตัวละครไม่สามารถมองเห็นตัวละครศัตรูและมีพลังชีวิตต่ำแล้วตัวละครนั้นจะถอยกลับไปยังพื้นที่ "ฐาน" / วางไข่

หมายเหตุ: บอทจะไม่ถอยในช่วงกลางของการต่อสู้ บอทนี้จะไม่ยิ้ม

ฉันใช้regexต่อไปนี้บน regexr.com เพื่อแปลงรหัส Java ของฉันเป็นบล็อกรหัสที่จัดรูปแบบ

รหัสด้านล่างมีการแสดงความคิดเห็นดังนั้นจึงควรเข้าใจง่าย หากคุณมีคำถามหรือคำชี้แจงใด ๆ เกี่ยวกับวิธีการทำงานคุณสามารถปิงฉันในห้องสนทนา Battle of the Fellowships !

แก้ไข: ฉันแก้ไขข้อผิดพลาดเล็กน้อยในโปรแกรมของฉันเพื่อปรับการเคลื่อนไหวของบ็อต (ไปข้างหน้า | ข้างหลัง) ขึ้นอยู่กับตำแหน่งที่เริ่มต้น ฉันลืมที่จะทำสิ่งนี้ดังนั้นฉันจึงแก้ไขได้ในตอนนี้

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
ทำให้คนขี้ขลาดอาย การทำงานที่ดี
Moogie

4

Derailer

ต้องลบมันสองครั้งเพราะฉันมีข้อผิดพลาดทางตรรกะมากมาย : P

อันนี้แน่นอนสามารถทำให้แผนของคุณตกราง ;)

ทีมงาน:

  • ตัวละคร 1 ตัวที่มีค่าCritical , Buff , StrongและQuickเพื่อกำจัดศัตรูออกไปอย่างรวดเร็วในขณะที่ยากที่จะเอาชนะ +25 STR, +2 AGI, +3 INT
  • 1 ตัวละครกับเคลฟเวอร์ , เคลฟเวอร์ , RestoreและZap อยู่ข้างหลังในฐานะผู้สนับสนุนและฟื้นฟูสุขภาพของเพื่อนร่วมทีมใด ๆ ที่ HP ต่ำและสามารถโจมตีและป้องกันตัวเองได้ตามต้องการ +14 STR, +3 AGI, +3 INT
  • 1 ตัวละครที่มีTrueSight , เดือย , Evasiveและสาน ไม่ใช่เรื่องง่ายที่จะตีและถ้าคุณทำหรือถ้าคุณเข้าใกล้เกินไปก็จะเห็นคุณและนัดหยุดงาน +13 STR, +3 AGI, +4 INT
Derailer.java

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

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

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

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

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

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

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

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

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

        return action;
    }

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

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

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

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

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

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

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

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

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

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

การเปลี่ยนแปลงของทีมที่น่าสนใจ! ทำให้ผมอยากจะลองสำหรับการกระทำบางทีมที่สูงขึ้น :)
Moogie

ขอบคุณ ฉันพยายามรวมพลังเข้าด้วยกันในทีม แต่ตัวละครตัวที่สามดูเหมือนจะไม่เข้าท่าเลย บางทีฉันอาจปรับปรุงกลยุทธ์นี้ในบอทในอนาคต
ทีเอ็นที

น่าสนใจการแทนที่อักขระที่สามด้วยสำเนาตัวแรกทำให้ได้ผลลัพธ์ที่ดีขึ้นมาก
ทีเอ็นที

4

SniperSquad

ทีมสไนเปอร์ประกอบด้วย:

  • 1 Spotter (ติดตั้งอุปกรณ์ตรวจจำตำแหน่งที่ดีที่สุดที่มีอยู่ทำให้ภาพรวมของเกือบทั้งแผนที่)
    • STR: 25; AGI: 5; INT: 5
    • ห่างไกลสายตา , ไกลสายตา , ไกลสายตา , ไกลสายตา
  • 2 Shooters (พร้อมกับปืนไรเฟิลซุ่มยิงเป้าหมายใหม่ล่าสุดข้อเสียเปรียบเพียงอย่างเดียวคืออัตราการยิงช้า)
    • STR: 25; AGI: 5; INT: 5
    • สาน , สำคัญ , สำคัญ , สำคัญ

คุณสามารถใช้อักขระเดี่ยวซ้ำได้จากที่นี่ในทีมของคุณตราบใดที่คุณเพิ่มอักขระอย่างน้อยหนึ่งตัวที่ไม่ปรากฏที่นี่

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, snipers ของคุณออก snipe snipers ขี้ขลาดตาของฉัน :) ทำงานได้ดี
Moogie

3

มนุษย์หมาป่า

ฉันไม่ใช่คนที่เก่งที่สุดในการเขียนตัวเลือกตัวเลือก AI โดยเฉพาะอย่างยิ่งสำหรับชุดกฎที่ซับซ้อนเท่านี้ เมื่อรวมกับความสามารถต่ำในการดู gamestate และสังเกตการตัดสินใจของนักแสดง (และด้วยผลลัพธ์ที่แตกต่างกันเล็กน้อยระหว่างการวิ่งมีความสามารถเพียงเล็กน้อยในการคำนวณระยะขอบสำเร็จของการเปลี่ยนแปลงเล็กน้อยเพื่อปรับปรุงตรรกะ AI) แต่ฉันก็สามารถทำได้ การเลือกความสามารถ / คุณสมบัติที่เหนือกว่าที่ครอบงำชุดบ็อตที่มีอยู่

ใช้Ranged , Swipe , StrongและWerewolfและใช้ตรรกะ AI เดียวกันกับLongSwordแม้ว่าจะมีการเปลี่ยนแปลงเล็กน้อย

ยากที่จะเลือกค่าที่เหมาะที่สุดเนื่องจากบางครั้งการเปลี่ยนแปลงอาจทำให้การเปลี่ยนจาก "ดีที่สุด" เป็น "แย่ที่สุด" Health-Retreat-threshold อยู่ที่ 50 ที่นี่ แต่ดูเหมือนว่าค่าใด ๆ ระหว่าง 10 ถึง 70 จะให้ผลลัพธ์เหมือนกัน

PlayerWerewolf.java

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

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

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

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

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

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

        ReadonlyAction current = null;

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

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

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

        return current;
    }

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

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

        }

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

        return location;
    }

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

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

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

มีปัญหาอยู่สองสามข้อ (การประกาศแพ็คเกจรวมถึงการไม่ใส่ชื่อไฟล์ในบรรทัดแรก) และฉันแก้ไขปัญหาเหล่านั้น ที่กล่าวว่าฉันไม่สามารถรับคลาสคงที่ที่จะผ่านคอมไพเลอร์ของฉัน ... ดูมัน
Nathan Merrill

ฉันคิดออก: คุณพลาดการนำเข้า:import fellowship.characters.EnemyCharacter;
นาธานเมอร์ริลล์

@NathanMerrill ฉันพยายามที่จะรวมคลาสที่สองเข้ากับคลาสภายในภายนอก Eclipse นั่นอาจเป็นสิ่งที่เกิดขึ้น
Draco18s

ดี! คุณใช้ฟังก์ชั่นการเคลื่อนไหวของฉันเองจาก LongSword!
Kritixi Lithos

@KritixiLithos ใช่ฉันมีปัญหาในการเขียนส่วน "ai" ของสิ่งต่าง ๆ ดังนั้นฉันจึงติดอยู่ที่ง่าย ๆ เพียงแค่มีจุดเริ่มต้นและมันทำงานได้ดีจริงๆ ฉันพยายามที่จะเล่นกับพวกเขาเพื่อดูว่าฉันสามารถทำให้ดีขึ้นได้หรือไม่เพราะพวกเขาจะเดินต่อไปหากพวกเขาไม่เห็นใครเลยแม้ว่าฝ่ายตรงข้ามจะอยู่ข้างหลังพวกเขา แต่ก็ไม่ได้ทำอะไรมากมาย ข้อแตกต่าง ส่วนใหญ่เป็นเพราะทั้งหมาป่าและดาบยาวยังไม่มีการต่อต้านการล่องหน
Draco18s

2

Railbender

บอทนี้เป็นเวอร์ชั่นของDerailerที่มีตัวละครตัวที่สามแทนที่ด้วยสำเนาตัวแรก มันให้ผลลัพธ์ที่ดีกว่าเมื่อเทียบกับ Derailer

ในขณะที่สร้าง Derailer ฉันต้องการให้ความสามารถของตัวละครแต่ละตัวที่จะประสานกันได้ดี การมีตัวละครตัวหนึ่งที่มี HP สูงและพลังโจมตีและตัวละครอีกตัวที่มีแอคชั่น Restore นั้นทำงานร่วมกันได้ดี อย่างไรก็ตามดูเหมือนว่าตัวละครที่สามจะเข้ากันไม่ได้ นั่นอาจเป็นเหตุผลหลักที่ทำให้ Derailer ไม่ได้ผลลัพธ์ที่ดี ดังนั้นฉันคิดว่าการมีตัวละครตัวที่สามที่สามารถทำงานได้ดีและได้รับประโยชน์จากคนอื่น ๆ จะเป็นความคิดที่ดีกว่า

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

! ที่น่าตื่นตาตื่นใจ นี่เป็นวิธีที่ยากกว่า Derailer
Kritixi Lithos

2

Noob/*Destroyer*/

ใช้Strong * 2, RegenerateและStun ( ทำให้สตันเป้าหมายสำหรับการกำจัด 300 ต่อไป)

สถิติ

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

AI

รหัสส่วนใหญ่ของ Noob นั้นมาจาก LongSword ของฉัน

กลยุทธ์

เมื่อตัวละครเห็นตัวละครของศัตรูเป็นลำดับแรกสิ่งสำคัญคือการ Stunning ศัตรูก่อนแล้วจึงแบ่งศัตรูในขณะที่พวกมันถูกสตัน และด้วยความแข็งแรงและการฟื้นฟูสูง Noob น่าจะสามารถอยู่รอดได้จนกว่าจะสามารถใช้ Stun ได้อีกครั้ง

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

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

public class Noob/*Destroyer*/ extends Player {

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

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

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

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

        ReadonlyAction readonlyAction = null;

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

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

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

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

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

        return readonlyAction;
    }

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

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

        }

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

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

2

ผนังห้องนั่งเล่น

กำแพงไม้ที่มีชีวิตที่สามารถเดินไปตามสนามรบและโจมตีศัตรูที่เข้ามาและระบายน้ำออกจากพวกมันเพื่อเสริมสร้างสุขภาพที่แข็งแรง ระบบรากของมันสามารถตรวจจับการสั่นสะเทือนทำให้มันสามารถพุ่งออกมาแม้กระทั่งศัตรูที่มองไม่เห็น มันประกอบด้วย:

  • 2 สาขา : STR 35, AGI 5, INT 5, Strong , Buff , Buff , Absorb
  • 1 ราก : STR 25, AGI , 5, INT , 5, True Sight , Buff , Buff , Absorb

AI นั้นง่ายอย่างไม่น่าเชื่อ: ค้นหาศัตรูที่อยู่ใกล้กับทีมที่สุดแล้วกำแพงทั้งหมดจะมุ่งเน้นไปที่ศัตรูตัวเดียว มีเพียงภาวะแทรกซ้อนเล็กน้อย: หากไม่มีศัตรูอยู่ในสายตาให้เดินไปที่มุมสุ่มของและ / หรือศูนย์กลางของแผนที่ (ดังนั้นในที่สุดก็ตามล่าศัตรูที่ซ่อนตัวอยู่); หากศัตรูไม่สามารถเข้าถึงได้ให้โจมตีมันแม้ว่าจะไม่ใช่ศัตรูที่เรากำลังกำหนดเป้าหมาย (แต่ต้องการที่จะมุ่งเน้นไปที่ศัตรูที่เรากำลังกำหนดเป้าหมาย

ทีมทำได้ดีอย่างไม่น่าเชื่อ ในการจำลองทีมเดียว (ที่มีอยู่ในขณะที่เขียน) ที่สามารถเอาชนะมันได้คือ RogueSquad และแม้กระทั่งในบางครั้งก็ไม่เสมอไป บางครั้งก็ไม่อาจทำลายได้อย่างง่ายดาย

เหตุผลพื้นฐานสำหรับความสำเร็จของทีมคือเนื่องจากคอมโบของ Buff × 2 และ Absorb; นี่หมายความว่าทุกครั้งที่เราโจมตีศัตรูหลัก STR เราได้รับ 40 HP ในระยะสั้นอย่างมีประสิทธิภาพ (เพียง 10 HP ในระยะยาวเนื่องจากการฟื้นฟูที่เพิ่มขึ้นจาก STR ที่ถูกขโมย แต่หลังจากนั้นการต่อสู้ควรจบลง และการฟื้นฟูตามธรรมชาติของเราน่าจะทำให้เราหลั่งไหลท่วมท้น) และด้วยอัตราการฟื้นฟูตามธรรมชาติที่ 12.5 หรือ 17.5 นอกเหนือจากนั้นมันเป็นไปไม่ได้ที่จะทำความเสียหายอย่างรวดเร็วพอที่จะทันกับการฟื้นฟู (ทีม AGI และใช้กลยุทธ์ แต่ยังไม่มีใครสร้างหนึ่งในนั้น) { อัปเดต : เห็นได้ชัดว่าคอมโบนี้ใช้งานไม่ได้จริง (ดูดซับเพียง 10 HP) แต่ทีมจะชนะอย่างไรก็ตามในขณะเดียวกันหากศัตรูไม่ได้STR- หลัก, พวกเขาไม่ต้องการรับความเสียหาย 25 หรือ 35 ครั้งซ้ำแล้วซ้ำอีก (และในความเป็นจริงอาจจะเน้นไปที่การตีหนึ่งครั้ง); และถ้าศัตรูเป็น INT-primary และใช้คาถาเพื่อปกป้องตัวเอง (สวัสดีคงกระพัน!) ในที่สุด Absorb จะระบาย MP ลงไปจนถึงจุดที่พวกเขาไม่สามารถใช้สกิลได้อีกต่อไป (นอกจากนี้เราไม่มีอะไรต้องกลัวจากคาถาส่วนใหญ่คูลดาวน์ของพวกเขานั้นยาวเกินไปสำหรับความเสียหายที่จะแซงหน้าการฟื้นฟูของเราข้อยกเว้นหลักคือ Trap ซึ่งยังไม่มีใครทำงานและ Poison ซึ่งใช้เวลานานกว่า 1,000 หรือ 1400 HP แต่ทำงานได้ถ้ากำแพงไม่ตีลูกล้อก่อน) True Sight ยังคงเป็นความสามารถเดียวที่สามารถเอาชนะศัตรูที่มองไม่เห็นได้ (Track doesn '

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

The Dark Absorbers เป็นพี่น้อง 2 คนซึ่งดูดซับพลังชีวิตของเหยื่อของพวกเขา:

  • Oracle Absorber (สามารถมองเห็นศัตรูที่มองไม่เห็น)
    • STR: 25; AGI: 5;INT: 5
    • TrueSight , Flexible , Ranged , Absorb
  • โช้คด่วน (สามารถดูดซับได้เร็วกว่าพี่ชายของเขา)
    • STR: 25; AGI: 5;INT: 5
    • ด่วน , มีความยืดหยุ่น , Ranged , ดูดซับ

พวกเขาจะมาพร้อมกับ Darkness Cloud ที่กำลังเติบโต เมื่อถึงมวลวิกฤตจะเริ่มฆ่าศัตรู

  • เมฆมืด
    • STR: 5; AGI: 5; INT: 25
    • Clone , Zap , Darkness

คุณสามารถใช้อักขระเดี่ยวซ้ำได้จากที่นี่ในทีมของคุณตราบใดที่คุณเพิ่มอักขระอย่างน้อยหนึ่งตัวที่ไม่ปรากฏที่นี่

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

0

LongSwordv2

"คุณสามารถเรียกใช้ แต่คุณซ่อนไม่ได้ ... " - LongSwordv2

ใช้ช่วงระยะเวลา , มีความยืดหยุ่น , ด่วน , TrueSight

บอทนี้คือ เหมือนกับ LongSwordv2 ทุกประการยกเว้นว่าจะใช้ TrueSight แทน Strong

เมื่อเห็นการเพิ่มขึ้นของบ็อตที่มองไม่เห็นฉันตัดสินใจที่จะสร้างบอทที่มุ่งเน้นที่จะนำพวกมันออกเพราะพวกมันไม่สามารถตรวจจับได้โดยบ็อตจำนวนมาก ด้วยระยะยาวและช่วง Slice ที่ยืดหยุ่นและ Double Slice ActionAbility ที่ยืดหยุ่น LongSwordv2 น่าจะสามารถสร้างความเสียหายอย่างหนักก่อนที่ตัวละครศัตรูจะเข้าสู่ช่วงการแบ่ง และในขั้นตอนการทดสอบของมันฉันจะบอกว่ามันชนะกับทีมที่มีตัวละครที่มองไม่เห็นเป็นส่วนใหญ่

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

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

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

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

    private boolean together = false;

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

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

        ReadonlyAction current = null;

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

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

        println(current.getName());

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

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

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

        return current;
    }

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

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

        }

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

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

        return location;
    }

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

การดาวน์โหลดบ็อตนี้ล้มเหลวเนื่องจากส่วนหัวหายไป
Sleafar

@Sleafar เราไปแล้ว ... เพิ่มมัน!
Kritixi Lithos
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.