21
\$\begingroup\$

CLOSED to competing entries

New bots are welcome but are not part of the main competition and probably won't be officially tested.

You stand on a dusty stretch of code, watching the other programs stare at you. Will they shoot? How much ammunition do they have left? All you know is that you want to be the last one standing.

Before the Game

You will be given 10 points to distribute between HP, Dexterity, Armor, and Speed. You may distribute up to 10 points between these categories (integer amounts only). You may put 0 points into a category.

  • Your HP will equal 10 plus the number of points you put in HP.
  • Your Dexterity (chance of dodging a hit) is equal to 0.04 times the number of points you put in Dexterity. For example, if you put 8 points in Dex, you would have a .32 chance of dodging a hit.
  • Your Armor (chance of taking 1 less damage from a hit) is equal to .1 times the number of points you put into Armor.
  • Your Speed is simply equal to the number of points you put in Speed.
  • You always start with 2 ammo.

Gameplay

This game is turn-based. Agents will be ordered based on their speed, with ties being broken at random before the game begins. Each agent will take their turn, choosing one of the three move options and executing it, then the next player will go. Unlike many KoTHs similar to this one, moves are NOT all executed at the same time.

On each agent's turn, there are three possible moves:

  • Heal: If not at maximum HP, gain 1 HP.
  • Reload: Gain 2 ammo.
  • Shoot: Choose another agent. That agent rolls for dexterity (creates a random real, and wins the roll if their dexterity is higher). If they win the roll they dodge and take 0 damage. Otherwise, they roll for armor. If they win, they take a random amount of damage from 0 to 3, while if they lose, they take a random amount of damage from 1 to 4. All damage is integers.

Once an agent's HP is less than 0, it is out and can no longer take actions. Last agent surviving wins the round.

Scoring

Each round, if there were X agents, your agent receives X points for each agent below it, plus an extra 2 points if they win. Therefore, in a 10 player game, the top agent would get 11 points, the second would get 8, and so on. As long as all the agents run fast, I should be able to run thousands (maybe hundreds of thousands) of tests, so the scores should be well-distributed. All scores are divided by the number of rounds played for ease of use.

I/O

All players will create a class which extends PlayerClass. As an example, you can look at any of the four (non-competing) bots created in the github. Each bot will have a class constructor which initializes the stats picked earlier and a makeMove() function, which determines what action to take and who is the target. For input, you have access to the following functions:

  1. getHP() (gets current HP)
  2. getAmmo() (gets ammo amount)
  3. getMaxHP() (gets your max HP)
  4. getDex() (gets dexterity)
  5. getArmor() (gets armor)
  6. getSpeed() (gets speed, you MAY call this on other players)
  7. getPlayers() (gets list of all players)
  8. getShotAt() (gets the PlayerClass object who you shot at most recently)
  9. getShotBy() (gets the PlayerClass object who shot at you most recently)
  10. getAliveEnemies() (gets a list of all players who have 1 or more HP and are not you)
  11. deadQ() (gets whether a player is dead, you MAY call this on other players)
  12. compareTo(PlayerClass p) (gets p's speed minus your speed)
  13. getWhoShotYou()(gets all bots who shot you (in a random order) since the last time you called the move() action)

The lists of players (getPlayers and getAliveEnemies) should be in turn order, so that allows you to check who will shoot next and/or solve ties in speed.

Rules for Bounty

If, at the end of the challenge (May 6, 20:00 GMT), the current winning bot is in the lead by at least 1 point AND in no way targets specific bots/acts differently based on the bots it faces, that bot will be given the bounty.

Otherwise, if a bot posted after the bounty satisfied this condition at one point, the most recent bot to do so will receive the bounty.

If no bot ever managed to meet the required conditions, the bounty will be awarded to the winner of the KoTH.

You may NOT delete bots you submitted previously to make your bot perform better. If you will delete a bot, I recommend deleting one of your worst ones.

As an example, Destroyer would currently meet the no-targeting requirement (since it simply shoots the first bot in the turn order), but only is leading by around .6 points, so doesn't meet the point requirement.

Rules

Submissions in Java only. As long as the submission is simple enough, I might be able to translate from pseudocode/some other language to Java though.

You may only enter 5 bots at any one time, and your bots may not collaborate. Feel free to submit more than 5, but when you submit a new one, set another one as noncompeting.

You MAY design your bots to target specific bots and/or predict what they will do, as long as it doesn't massively help your other bots.

No calling any methods of other entities, except for the ones called out above. The biggest problem here is takeDamage(), which has to be public so it can be called when someone is shot.

No redefining any functions/variables of PlayerClass besides makeMove().

The subclass constructor must be of the form super(x,y,z,a); and cannot overwrite the requirements in the PlayerClass constructor (for example, allowing yourself to spend more than 10 points).

Feel free to define other methods of your class and/or create variables for memory purposes. Between each game, your bot will be reset, so it will only affect each individual game.

Don't make your code take forever. This KOTH is pretty lightweight, so there shouldn't be a need for complex algorithms.


Leaderboard (FINAL STANDINGS)

ChargeBot->0.10870789129210871

SmartBot->1.151028848971151

Tank->3.4236785763214237

RageBot3->3.693295306704693

OneVsOneMeBro->3.7322532677467324

FriendlyAndHealthyBot->5.668694331305669

HpKing->7.525759474240526

FriendlyNotFriendlyBot->7.694217305782694

WinnerBot->8.004405995594004

NormalBot->8.276454723545276

Destroyer->8.732566267433732

StrategicBot->8.91998708001292

Dexter->9.556512443487556

WhatABot->10.622109377890622

FocusFireBot->10.945748054251945

FocusFireBot takes the victory!


The controller and all current agents can be found here: https://github.com/romanpwolfram/GunfightKoTH/tree/main

Challenge name by tjjfvi.

\$\endgroup\$
26
  • 1
    \$\begingroup\$ I'm undecided on whether I want that to be a feature. \$\endgroup\$
    – Romanp
    May 2 at 12:55
  • 1
    \$\begingroup\$ @RyanFu A bit late to change that now imo, since almost all existing bots would have to be modified. You can keep such a list based on the bot that last shot at you, as is what I did in my HpKing-bot. If multiple bots shot at you in a single round, it will only know about the last bot that shot you that round, though. \$\endgroup\$ May 2 at 15:31
  • 1
    \$\begingroup\$ @KevinCruijssen You could overload a method for it to return either a specific bot or a list, but in the end, it's the OP's decision \$\endgroup\$ May 4 at 5:18
  • 2
    \$\begingroup\$ Upvote this message if you want a new method to be added. \$\endgroup\$
    – Romanp
    May 4 at 13:15
  • 2
    \$\begingroup\$ Upvote this message if you don't want the new getShotBy method to be added. \$\endgroup\$
    – Romanp
    May 4 at 13:15

17 Answers 17

6
\$\begingroup\$

#1 - FriendlyNotFriendlyBot

package standoffKOTH;

import java.util.Random;


public class FriendlyNotFriendlyBot extends PlayerClass
{
    Random rand = new Random();

    public FriendlyNotFriendlyBot()
    {
        super(10, 0, 0, 0);
    }

    @Override
    protected int makeMove()
    {
        if (this.getAmmo() < 1)
            return this.move('r', this);
        else if (this.getShotBy() != null && !this.getShotBy().deadQ())
            return this.move('s', this.getShotBy());
        else if (this.getAmmo() < 10)
            return this.move('r', this);
        else if (this.getHP() < 20)
            return this.move('h', this);
        else
            return this.move('s', this.getAliveEnemies().get(rand.nextInt(this.getAliveEnemies().size())));
    }
}

This bot tests these conditions, in order:

  • If it has no ammo, it refills.
  • If it is getting attacked, and the opponent is still alive, it will retaliate.
  • If it has less than 10 ammo, it refills.
  • If it has less than 20 health, it heals.
  • Else, it shoots at random.

There is a flaw: if it got attacked twice or more in the same turn, but the last attacker is dead, it will not retaliate because only the last attacker can be viewed.

Trivia: There is an interesting Prisoner's dilemma with the Speed of the bots. The thing is the most effective strategy is to have the speed to 0, but if someone increases his speed to 1, he will be faster and therefore win. But in this case, others will increase their speed to 2, and progressively to the maximum 10. At this point however, a player can set his speed back to 0 and win easily.

#2 - FriendlyAndHealthyBot

package standoffKOTH;

import java.util.Random;


public class FriendlyAndHealthyBot extends PlayerClass
{
    Random rand = new Random();

    public FriendlyNotFriendlyBot()
    {
        super(10, 0, 0, 0);
    }

    @Override
    protected int makeMove()
    {
        if (this.getAmmo() < 1)
            return this.move('r', this);
        else if (this.getShotBy() != null && !this.getShotBy().deadQ())
            return this.move('s', this.getShotBy());
        else if (this.getHP() < 20)
            return this.move('h', this);
        else
            return this.move('s', this.getAliveEnemies().get(rand.nextInt(this.getAliveEnemies().size())));
    }
}

A slight variation of FNFB.

This bot tests these conditions, in order:

  • If it has no ammo, it refills.
  • If it is getting attacked, and the opponent is still alive, it will retaliate.
  • If it has less than 20 health, it heals.
  • Else, it shoots at random.
\$\endgroup\$
4
  • 1
    \$\begingroup\$ You are currently in second! \$\endgroup\$
    – Romanp
    Apr 25 at 15:23
  • 1
    \$\begingroup\$ Tested with fixed version, got third (.004 points behind HpKing) \$\endgroup\$
    – Romanp
    Apr 28 at 13:56
  • \$\begingroup\$ Both of your bots have the same class name? \$\endgroup\$ Apr 29 at 20:31
  • \$\begingroup\$ Oops, Ctrl-C + Ctrl-V failed me! \$\endgroup\$
    – loggeek
    May 1 at 6:51
4
\$\begingroup\$

Destroyer

class Destroyer(PlayerClass):
    def __init__(self):
        super().__init__(10, 0, 0, 0)
    
    def makeMove(self):
        if self.health == 1:
            return "h"
        if self.ammo < 3:
            return "r"
        return "s", getAliveEnemies.get(0)  # destroy first enemy

Java version:

package standoffKOTH;

public class Destroyer extends PlayerClass {
    public Destroyer()
    {
        super(10,0,0,0);
    }
    protected int makeMove()
    {
        if(getHP()<2)
        {
            return move('h',this);
        }
        else if(getAmmo()<3)
        {
            return move('r',this);
        }
        else
        {
            return move('s',getAliveEnemies().get(0));}}}
```
\$\endgroup\$
4
\$\begingroup\$

Example Bots

These bots will not compete, but will likely be included in the test rounds for the first few bots and have a few useful examples.

Bot 0: ExampleBot

Literally just heals, no stats.

package standoffKOTH;

public class ExamplePlayerClass extends PlayerClass {

    public ExamplePlayerClass()
    {
        super(0,0,0,0);//put stats here
    }
    protected int makeMove()
    {
        return this.move('h',this);//It doesn't matter who is target with non-shoot actions, so just self here
    }
}

Bot 1: RevengeBot

Shoots whoever shot it last. Is happy to just reload until it is shot. If whoever shot it is now dead, it goes into a rage and picks someone at random to shoot.


package standoffKOTH;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Player1Class extends PlayerClass {

    public Player1Class()
    {
        super(0,8,1,1);
    }
    protected int makeMove()
    {
        List<PlayerClass> g = new ArrayList<PlayerClass>();g= this.getAliveEnemies();
        if((int)getHP()<6) 
        {return this.move('h', this);} 
        else {
            if (getAmmo()<1)
            {return this.move('r',this);}
            else {
            if(getShotBy()!=null&& g.contains(getShotBy())) {
                if(getAmmo()>0){
                    return this.move('s',(PlayerClass) getShotBy());}
                else{
                    return this.move('r',this);}}
            else{
                if(getShotBy()!=null&&(getShotBy()).deadQ()&&getAliveEnemies().size()!=0)
                {Collections.shuffle(g);return this.move('s',g.get(0));}
                else {return this.move('r', this);}}
    }}}
}


Bot 2: RandomBot

Will pretty much shoot anyone who's still alive, other than itself, of course. It may be random, but it's no EmoWolf.

package standoffKOTH;

import java.util.List;
import java.util.Random;

public class Player2Class extends PlayerClass {
    Random rand = new Random();
    public Player2Class()
    {
        super(9,0,0,1);
    }
    protected int makeMove()
    {
        List<PlayerClass> z = getAliveEnemies();
        if(getAmmo()>0) {return this.move('s',z.get(rand.nextInt(z.size())));}{return this.move('r',this);}
    }
}

Bot 3: DedicatedBot

Just like RevengeBot, DedicatedBot won't shoot until shot. However, DedicatedBot will shoot a specific bot until they die, then switch to another random bot. DedicatedBot also won't heal (unless they are already dead, in which case it doesn't matter). It's just that dedicated.

package standoffKOTH;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Player3Class extends PlayerClass {
    PlayerClass firstShot = null;
    public Player3Class()
    {
        super(5,0,5,0);
    }
    protected int makeMove()
    {
        List<PlayerClass> g = new ArrayList<PlayerClass>();g= this.getAliveEnemies();
        if(getShotBy()!=null&&firstShot==null) {firstShot = (PlayerClass) getShotBy();}
        if(getHP()<1) 
        {return this.move('h', this);} 
        else {
            if (getAmmo()<2)
            {return this.move('r',this);}
            else {
            if(firstShot!=null&& g.contains(firstShot)) {
                if(getAmmo()>0){
                    return this.move('s',firstShot);}
                else{
                    return this.move('r',this);}}
            else{
                if(getShotBy()!=null&&((PlayerClass)getShotBy()).deadQ()&&this.getAliveEnemies().size()!=0)
                {Collections.shuffle(g);return this.move('s',g.get(0));}
                else {return this.move('r', this);}}
    }}}
}

Bot 4: PsychopathBot

The sibling of DedicatedBot, PsychopathBot brings its philosophy to the extreme. It picks a bot at the beginning of the game and attempts to kill them. Once they are dead, it checks if it got shot by anyone (which it should have been). If it did, it picks them as its new target and does its best to take them out. Otherwise, it just chooses at random again and starts shooting.

package standoffKOTH;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class Player4Class extends PlayerClass {
    Random random = new Random();
    PlayerClass firstShot = null;
    public Player4Class()
    {
        super(10,0,0,0);
    }
    protected int makeMove()
    {
        List<PlayerClass> g = new ArrayList<PlayerClass>();g= this.getAliveEnemies();
        if (this.getAliveEnemies().contains(firstShot)) {;} else if(getShotBy()!=null) {firstShot = getShotBy();} else {firstShot = this.getAliveEnemies().get(random.nextInt(this.getAliveEnemies().size()));}
        if(getHP()<1) 
        {return this.move('h', this);} 
        else {
            if ((int)getAmmo()<2)
            {return this.move('r',this);}
            else {
            if(firstShot!=null&& g.contains(firstShot)) {
                if((int)getAmmo()>0){
                    return this.move('s',firstShot);}
                else{
                    return this.move('r',this);}}
            else{
                if(getShotBy()!=null&&getShotBy().deadQ()&&this.getAliveEnemies().size()!=0)
                {Collections.shuffle(g);return this.move('s',g.get(0));}
                else {return this.move('r', this);}}
    }}}
}

Generally, RevengeBot, DedicatedBot, and PsychopathBot split on subscore relatively closely, while RandomBot takes last. However, RevengeBot wins 50% of the games, while DedicatedBot wins around 10% (and RandomBot only wins 5%). This means that RevengeBot scores around 18000 points in a 5000 round match, while PsychopathBot scores 17000, DedicatedBot 16000, and RandomBot 3500.

\$\endgroup\$
3
\$\begingroup\$

Tank

package standoffKOTH;
import java.util.concurrent.ThreadLocalRandom;

public class Tank extends PlayerClass {

    public Tank() {
        super(3, 1, 5, 1);
    }

    @Override
    protected int makeMove() {
        if (getHP() < 6 && ThreadLocalRandom.current().nextBoolean()) {
            return move('h', this);
        } else if (getAmmo() == 0) {
            return move('r', this);
        } else {
            PlayerClass lastShot = getShotBy();
            if (lastShot == null) {
                return move('r', this);
            } else {
                return move('s', lastShot);
            }
        }
    }
}

Simple bot with high armor and health. If its health is below 6, it has a 50% chance of healing itself. If it has an empty holster, it reloads. Otherwise, if nobody fired it it, it also reloads the holster. If someone fired at the bot, it fires back.

\$\endgroup\$
3
\$\begingroup\$

HpKing

package standoffKOTH;

import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

public class HpKing extends PlayerClass{
    // Keep a map of the amount of times an enemy (last) shot at us
    TreeMap<String, Integer> shotBy = new TreeMap<>();
    int previousHp;

    public HpKing(){
        super(10,0,0,0); // Health ftw!
        previousHp = getHP();
    }

    protected int makeMove() {
        // Update the shotBy-map with the latest enemy that shot at us
        if(getShotBy() != null && previousHp!=getHP()){
            shotBy.merge(getShotBy().getClass().toString(), 1, Integer::sum);
            previousHp = getHP();
        }
        // Remove any enemies from the shotBy-map that are already dead:
        shotBy.keySet().removeIf(enemyStr -> getAliveEnemies().stream().noneMatch(instance -> instance.getClass().toString().equals(enemyStr)));

        // If there are more than two enemies left: heal if we're not full, otherwise reload to get more ammo
        if(getAliveEnemies().size()>2){

        }

        // If we're not full health:
        if (getHP() < getMaxHP()){
            // If there are more than two enemies are left: heal!!
            if (getAliveEnemies().size() > 2)
                return move('h', this);
            // If there is more than one enemy left: only heal if we've below 10 HP:
            if (getAliveEnemies().size() > 1 && getHP() < 10)
                return move('h', this);
            // If there is just a single enemy left: just keep shooting and reloading, and hope for the best
        }

        // If we didn't have to heal, but are out of ammo: reload
        if(getAmmo()<1)
            return move('r', this);

        // If we didn't had to heal and still have ammo left: shoot the enemy that most frequently shot at us
        // (with priority to the more recent one, if multiple enemies shot at us an equal amount of times before):
        // If no one shot at us yet: shoot at a random enemy:
        return move('s',
                shotBy.isEmpty() ? getAliveEnemies().get((int)(Math.random()*getAliveEnemies().size()))
                        : getAliveEnemies().stream()
                        .filter(enemy -> enemy.getClass().toString().equals(
                                Collections.max(shotBy.descendingMap().entrySet(), Map.Entry.comparingByValue()).getKey()))
                        .findFirst().get());
    }
}

This bot maxes out on HP for its 10 bonus values.
At the start, this bot's entire focus is to stay alive and keep its health at its maximum.
From the moment just two enemies are left alive: it's a bit more lenient with its health and starts focusing more on shooting (but still tries to keep its HP above 10 at all time).
From the moment just a single enemy is left: it just keeps reloading/firing and hopes for the best.

\$\endgroup\$
3
\$\begingroup\$

WinnerBot

class WinnerBot:
    def __init__(self):
        super.__init__(5, 4, 1, 0)
        
    def makeMove(self):
        if self.health < 4:
            return "h"
        if self.ammo < 1:
            return "r"
        return "s", getShotBy()
\$\endgroup\$
2
+100
\$\begingroup\$

FocusFireBot

package standoffKOTH;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class FocusFireBot extends PlayerClass {
    private PlayerClass focusTarget;
    private Map<PlayerClass, Integer> targetedBy = new HashMap<PlayerClass, Integer>();
    private List<PlayerClass> targetedLastRound = new ArrayList<PlayerClass>();
    protected int move = 0;
    private int movesPerRound = 250;

    public FocusFireBot() {
        super(0, 0, 10, 0);
    }

    protected int makeMove() {
        move++;
        if (deadQ()) {
            return 0;
        }
        // fully heal
        if (getHP() < getMaxHP()) {
            return this.move('h', this);
        }
        if (focusTarget != null && focusTarget.deadQ()) {
            focusTarget = null;
        }

        // if we have a target we kill or we die
        if (focusTarget != null) {
            if (getAmmo() < 1) {
                return this.move('r', this);
            } else {
                return this.move('s', focusTarget);
            }
        }

        List<PlayerClass> aliveEnemies = getAliveEnemies();
        // if only one enemy is left target it
        if (aliveEnemies.size() < 2) {
            focusTarget = aliveEnemies.get(0);
        } else {
            // if someone shot us twice in a row, we set him as target
            // if (getShotBy() != null && !getShotBy().deadQ()) {
            // focusTarget = getShotBy();
            // }
            List<PlayerClass> targetedThisRound = getWhoShotYou();
            for (PlayerClass player : targetedThisRound) {
                if (!player.deadQ()) {
                    if (targetedLastRound.contains(player)) {
                        focusTarget = player;
                    }
                    targetedBy.merge(player, 1, Integer::sum);
                } else {
                    targetedLastRound.remove(player);
                }
            }
        }
        // shoot if we where shot twice in a row
        if (focusTarget != null) {
            if (getAmmo() < 1) {
                return this.move('r', this);
            } else {
                return this.move('s', focusTarget);
            }
        }

        // fully heal
        if (getHP() < getMaxHP()) {
            return this.move('h', this);
        }
        // get enought ammo to sustain focused fire
        if (getAmmo() < 50 && getAmmo() + move < movesPerRound) {
            return this.move('r', this);
        }

        // focus on player who shot us the most
        List<Entry<PlayerClass, Integer>> shotByList = new ArrayList<>(targetedBy.entrySet());
        shotByList.sort(Entry.comparingByValue());
        Collections.reverse(shotByList);
        for (Entry<PlayerClass, Integer> playerE : shotByList) {
            PlayerClass player = playerE.getKey();
            if (player.deadQ()) {
                targetedBy.remove(player);
            } else {
                focusTarget = player;
                return this.move('s', focusTarget);
            }
        }

        //
        focusTarget = aliveEnemies.get(0);
        return this.move('s', focusTarget);
    }
}

If you get his attention there are only two possible outcomes: You or he dies!

Update 1

  • added move counter and made reload condition so that it does not reload if it has enough ammo for shooting until end --> reduces ties but overall little impact and requires knowlege of turns per round
  • went full armor and health is now most important (even more than shooting) --> more score but also more ties 😂

Last Udate

  • now picking of fastest bot than slowest
\$\endgroup\$
6
  • \$\begingroup\$ Welcome to Code Golf, and nice bot! \$\endgroup\$ May 5 at 12:45
  • \$\begingroup\$ Running tests now. Let's see how it does! \$\endgroup\$
    – Romanp
    May 5 at 13:12
  • \$\begingroup\$ Your bot is in second! It does cause a lot of ties, though, so some small improvements might be enough to get you the win. \$\endgroup\$
    – Romanp
    May 5 at 13:28
  • \$\begingroup\$ @RadvylfPrograms Hello, thank you. It is an interessting challenge so i had to join it \$\endgroup\$
    – Salasam
    May 6 at 16:41
  • \$\begingroup\$ You are around .1 points behind WhatABot \$\endgroup\$
    – Romanp
    May 6 at 16:59
2
\$\begingroup\$

NormalBot

class NormalBot(PlayerClass):
    def __init__(self):
        super().__init__(6, 1, 3, 0)
    
    def makeMove(self):
        if self.health < 5:
            return "h"
        if self.ammo < 4:
            return "r"
        
        if getShotBy() is None:
            return "rh"[self.health > 5]
        
        return "s", last_enemy_who_attacked_me()

Java version:

package standoffKOTH;
public class NormalBot extends PlayerClass {
    public NormalBot()
    {
        super(6,1,3,0);
    }
    protected int makeMove()
    {
        if(getHP()<5)
        {
            return move('h',this);
        }
        else if (getAmmo()<4)
        {
            return move('r',this);
        }
        else if (getShotBy()!=null)
        {
            return move('s',getShotBy());
        }
        else if (getHP()<5)
        {
            return move('h',this);
        }
        else
        {
            return move('r',this);
        }}}
\$\endgroup\$
2
\$\begingroup\$

OneVsOneMeBro

package standoffKOTH;

public class OneVsOneMeBro extends PlayerClass{
    public OneVsOneMeBro(){
        super(9,0,0,1); // Sacrifice 1 HP for 1 Speed
    }

    protected int makeMove() {
        // If there are more than one enemy left:
        // heal if we're not max, otherwise reload to get more ammo
        if (getAliveEnemies().size()!=1) {
            if (getHP()<getMaxHP())
                return move('h', this);
            else
                return move('r', this);
        }
        // If we're out of ammo: reload
        if (getAmmo()<1)
            return move('r', this);
        // Just a single enemy left, and we have ammo: shoot, shoot, shoot!
        return move('s', getAliveEnemies().get(0));
    }
}

As long as there is more than one enemy left: just heal if we're not max, otherwise gain ammo.
And as soon as just a single enemy is left: keep shooting, unless we have to reload.

Also uses a speed of 1, so it has a higher chance of going first in the final 1vs1 versus the bots with speed 0.

\$\endgroup\$
2
\$\begingroup\$

SmartBot

package standoffKOTH;

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

public class SmartBot extends PlayerClass {
 private PlayerClass lastFired;
 public SmartBot() {
  super(0, 5, 3, 2);
 }
 protected int makeMove() {

  if (getHP() < 5) {
   return this.move('h', this);
  }
  if (getAmmo() < 2) {
   return this.move('r', this);
  }
  List<PlayerClass> enemies = new ArrayList<PlayerClass>();
  enemies = getAliveEnemies();
  for (int i = 0; i < enemies.size(); i++) {
   PlayerClass enemy = enemies.get(i);
   if (!(enemy instanceof RageBot3) && enemy.equals(lastFired)) {
    return this.move('s', enemy);
   }
  }
  lastFired =getShotBy();
  if (getHP() < 10) {
   return this.move('h', this);
  }
  return this.move('s', enemies.get(0));
 }
}

Or perhaps not too smart.

As I mentioned before, I suck at Java, so please correct any glaring errors you see. Also, if this bot fails to work during any test runs, please notify me of the error so I can correct my code. Hopefully I got it right.

This bot is quite conservative. If its HP is below 5, it will heal itself.

If its ammo is below 2, it will reload. (But only if its HP is 5 or more.)

Otherwise... it first tries to see if the same bot fired at it twice in a row. Or at least it tries to. Unless two different bots fire at it in a row, lastFired will be the same as before, so essentially if nobody has fired at SmartBot for a long time, SmartBot will just keep firing at the last person who shot at it.

If the above condition is true, it fires at that bot. Otherwise...

If its HP is below 10, it just heals itself (as a precautionary measure), otherwise it just shoots at the first bot in the list that is getAliveEnemies().

And because RageBot3 seems to absolutely hate me, I have returned the favor.

So you see... pretty dumb after all.

\$\endgroup\$
2
  • \$\begingroup\$ Testing the bot right now, trying to see how well it does. \$\endgroup\$
    – Romanp
    Apr 22 at 15:02
  • \$\begingroup\$ You scored fourth, although it was relatively close in score to RevengeBot, DedicatedBot, and PsychopathBot (in order of score). It wins more games than DedicatedBot (by a pretty good margin) and scores more subscore than RevengeBot, but each of those bots is good enough at the other that they beat you out on total score. \$\endgroup\$
    – Romanp
    Apr 22 at 15:11
2
\$\begingroup\$

WhatABot

package standoffKOTH;

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

public class WhatABot extends PlayerClass {
    List<Class> randomShooters = List.of(FriendlyAndHealthyBot.class, FriendlyNotFriendlyBot.class, HpKing.class, RageBot.class, RageBot2.class, Player2Class.class);
    List<Class> wontShootFirst = List.of(Dexter.class, WhatABot.class, Tank.class, NormalBot.class);
    List<PlayerClass> shot = new ArrayList<>();
    int turn = -1;

    public WhatABot() {
        super(10, 0, 0, 0);
    }

    protected int makeMove() {

        if (this.deadQ()) {
            return 0;
        }
        shot.removeIf(PlayerClass::deadQ);
        turn++;

        // if there's only 1 enemy left, or I'm currently getting attacked, just spam shoot
        PlayerClass immediateTarget = null;
        if (this.getShotBy() != null && !this.getShotBy().deadQ()) {
            immediateTarget = this.getShotBy();
            // only retaliate if its not some bot randomly firing
            if (!shot.contains(immediateTarget)) {
                for (Class randomShooter : randomShooters) {
                    if (randomShooter.isInstance(immediateTarget)) {
                        immediateTarget = null;
                        break;
                    }
                }
            }
        }
        if (getAliveEnemies().size() == 1) {
            immediateTarget = this.getAliveEnemies().get(0);
            // if the bot won't shoot unless shot first, heal up and fill up ammo before engaging
            if (!shot.contains(immediateTarget) && !this.getWhoShotYou().contains(immediateTarget)) {
                for (Class bot : wontShootFirst) {
                    if (bot.isInstance(immediateTarget)) {
                        if (getHP() < getMaxHP()) {
                            return this.move('h', this);
                        } else if (getAmmo() < 40 && getAmmo() + turn < 250) {
                            return this.move('r', this);
                        }
                    }
                }
            }
        }
        if (immediateTarget != null) {
            if (getAmmo() == 0) {
                return this.move('r', this);
            } else {
                shot.add(immediateTarget);
                return this.move('s', immediateTarget);
            }
        }

        if (shot.size() > 0) {
            if (getHP() < 10) {
                return this.move('h', this);
            }
            if (getAmmo() == 0) {
                return this.move('r', this);
            }
            return this.move('s', shot.get(shot.size() - 1));
        }

        if (getHP() < getMaxHP()) {
            return this.move('h', this);
        }
        if (getAmmo() < 30 && getAmmo() + turn < 250) {
            return this.move('r', this);
        }


        // kill fast players first
        PlayerClass target = getAliveEnemies().get(0);
        if (target.getSpeed() <= this.getSpeed() && getAmmo() < 50 && getAmmo() + turn < 250) {
            return this.move('r', target);
        }
        shot.add(target);
        return this.move('s', target);
    }
}

Bot logic in comments.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Wins right now, but not eligible for the bounty with its anti-random-shooter code. \$\endgroup\$
    – Romanp
    May 2 at 16:59
1
\$\begingroup\$

RageBot

class RageBot(PlayerClass):
    def __init__(self):
        super().__init__(10, 0, 0, 0)
    
    def makeMove(self):
        if self.health < 5:
            return "s", last_one_who_attacked_me()
        return "r"

Java version:

package standoffKOTH;
import java.util.Random;
public class RageBot extends PlayerClass {
    Random rand = new Random();
    public RageBot()
    {
        super(10,0,0,0);
    }
    protected int makeMove()
    {
        if(getHP()<5&&getShotAt()!=null)
        {
            return move('s',getShotAt());
        }
        else if (getHP()>4)
        {
            return move('r',this);
        }
        else
        {
            return move('s',getAliveEnemies().get(rand.nextInt(getAliveEnemies().size())));}}}
\$\endgroup\$
1
\$\begingroup\$

RageBot3

class RageBot3(PlayerClass):
    def __init__(self):
        super().__init__(8, 1, 0, 1)
    
    def makeMove(self):
        target = getShotBy()
        if target.deadQ():
            for e in getAliveEnemies():
                if isinstance(e, SmartBot) and not e.deadQ():
                    target = e
                    return "r"
            
            return "s", getAliveEnemies().get(0)
        
        if self.health < 3:
            return "h"
        elif self.health < 10:
            return "s", target
        
        if self.ammo < 6:
            return "r"
        else:
            if self.health < 5:
                return "h"
            else:
                return "s", target

Java version:

package standoffKOTH;

import java.util.Random;

public class RageBot3 extends PlayerClass {
    Random rand = new Random();
    PlayerClass target = null;
    public RageBot3()
    {
        super(8,1,0,1);
    }
    protected int makeMove()
    {
        if(target==null||target.deadQ()) {
            if(getShotBy()!=null)
            {
                target = getShotBy();
            }
        else if(target==null||target.deadQ()) {
            for(PlayerClass p:getAliveEnemies())
            {
                if(p instanceof SmartBot)
                {
                    target=p;
                    break;
                }
            }
        }
        if(target==null||target.deadQ()) {
            target = getAliveEnemies().get(0);
        }
        }
        if(getHP()<3)
        {
            return move('h',this); 
        }
        else if (getHP()<10)
        {
            return move('s',target);
        }
        else if (getAmmo()<6)
        {
            return move('r',this);
        }
        else if (getHP()<5)
        {
            return move('h',this);
        }
        else
        {
            return move('s',target);
        }
    }
}
```
\$\endgroup\$
1
\$\begingroup\$

StrategicBot

It has different strategies depending on the dynamics of the game.

friendliness = remaining players / total number of players with a deduction if it is being attacked two times in a row by someone.

It targets the last player who attacks it or the next player in the current round.

It tends to heal and reload when the friendliness is higher.

package standoffKOTH;

public class StrategicBot extends PlayerClass
{
    int total;
    PlayerClass foe;

    public StrategicBot()
    {
        super(10, 0, 0, 0);
        this.total = this.getPlayers().size();
    }

    @Override
    protected int makeMove()
    {
        float friendliness;
        // The bot gets more aggressive when there are fewer players
        if (this.getAliveEnemies().size() == 1)
            friendliness = 0;
        else
            friendliness = (float) this.getAliveEnemies().size() / total;

        // When being repeatedly attacked by someone, act more aggressively
        if (this.foe != null && this.foe == this.getShotBy())
            friendliness -= 0.5;

        if (this.foe.deadQ())
            this.foe = null;

        PlayerClass nextTarget; 
        if (this.getShotBy() != null && !this.getShotBy().deadQ())
            nextTarget = this.getShotBy();
        else
            nextTarget = this.getAliveEnemies().get(0);

        if (this.getAmmo() == 0)
            return this.move('r', this);

        if (friendliness > 0.7) {
            if (this.getHP() < this.getMaxHP())
                return this.move('h', this);
            else if (this.getAmmo() < 15)
                return this.move('r', this);
        }
        else if (friendliness > 0.35) {
            if (this.getHP() < 12)
                return this.move('h', this);
            else if (this.getAmmo() < 8)
                return this.move('r', this);
        }
        else if (friendliness > 0.15) {
            if (this.getAmmo() < 2)
                return this.move('r', this);
            else if (this.getHP() == 3)
                // heal so there won't be a instakill
                // probably less valuable when HP < 3     
                return this.move('h', this);
        }
        // Does not do anything else when friendliness <= 0.15
        return this.move('s', nextTarget);
    }
}
```
\$\endgroup\$
1
  • \$\begingroup\$ Had to make a few minor changes to the code to make it run, but it looks good to me now. You are in second, beating the existing bot slate by a decently good margin but losing to WhatABot by quite a bit. \$\endgroup\$
    – Romanp
    May 3 at 13:41
1
\$\begingroup\$

Dexter

package standoffKOTH;

public class Dexter extends PlayerClass {
    public Dexter() {
        super(0, 10, 0, 0);
    }

    protected int makeMove() {
        if (getHP() < getMaxHP()) { // heal if we're not at full health
            return move('h', this);
        }

        if (getAmmo() == 0) { // reload if we have no bullets
            return move('r', this);
        } else if (getShotBy() != null) { // if someone shot at us, retaliate
            if (getAliveEnemies().contains(getShotBy())) { // only if they're alive
                return move('s', getShotBy());
            } else {
                return move('h', this); // heal otherwise
            }
        } else {
            return move('h', this); // heal if we cannot decide on an action
        }
    }
}

Dexter is a simple man. He puts all the points into his favorite skill Dexterity.

He also fixates on healing a ton now. After a ton of testing on my side this should make Dexter compete harder than before..

\$\endgroup\$
0
\$\begingroup\$

RageBot2

class RageBot2(PlayerClass):
    def __init__(self):
        super().__init__(8, 1, 1, 0)
    
    def makeMove(self):
        if self.health < 3:
            return "h"
        elif self.health < 10:
            return "s", last_one_who_attacked_me()
        
        if self.ammo < 6:
            return "r"
        else:
            if self.health < 8:
                return "h"
            else:
                return "s", last_one_who_attacked_me()

Java version:

package standoffKOTH;

import java.util.Random;

public class RageBot2 extends PlayerClass {
    Random rand = new Random();
    public RageBot2()
    {
        super(8,1,1,0);
    }
    protected int makeMove()
    {
        if (getHP()<3)
        {
            return move('h',this);
        }
        else if (getHP()<10&&getAmmo()>0&&getShotBy()!=null&&!getShotBy().deadQ())
        {
            return move('s',getShotBy());
        }
        else if (getAmmo()<6)
        {
            return move('r',this);
        }
        else if (getHP()<8)
        {
            return move('h',this);
        }
        else if (getShotBy()!=null&&getAmmo()>0&&!getShotBy().deadQ())
        {
            return move('s',getShotBy());
        }
        else if (getAmmo()<1)
        {
            return move('r',this);
        }
        else
        {
            return move('s',getAliveEnemies().get(rand.nextInt(getAliveEnemies().size())));}}}

This bot hopes to be RageBot, but smarter.

\$\endgroup\$
0
\$\begingroup\$

ChargeBot

class ChargeBot(PlayerClass):
    def __init__(self):
        super().__init__(5, 0, 0, 5)
    
    def makeMove(self):
        if self.ammo == 0:
            return "r"
        if self.health < 7:
            return "h"
        return "s", alive_enemies()[-1]  # target slowest
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.