33
\$\begingroup\$

This is a KOTH challenge for the dollar bill auction game in game theory. In it, a dollar is being sold to the highest bidder. Bids go up in increments of 5¢, and the loser also pays their bid. The idea is that both players escalate the bidding war far beyond the value of a dollar in order to cut their losses.

Let's hope your bots are smarter than that.

You will be creating a bot to play this game by extending the net.ramenchef.dollarauction.DollarBidder class. You must implement the nextBid method that returns your bot's next bid given the other bot's previous bid. If necessary, you can also use the newAuction method to reset for each auction with the class of the opponent's bot.

public abstract class DollarBidder {
    /**
     * Used by the runner to keep track of scores.
     */
    long score = 0;

    /**
     * (Optional) Prepare for the next auction.
     *
     * @param opponent The class of the opponent's bot.
     */
    public void newAuction(Class<? extends DollarBidder> opponent) {}

    /**
     * Bid on the dollar. Bidding ends if the bid is
     * not enough to top the previous bid or both bids
     * exceed $100.
     *
     * @param opponentsBid How much money, in cents,
     *  that the opponent bid in the previous round. If
     *  this is the first round in the auction, it will
     *  be 0.
     * @return How much money to bid in this round, in
     *  cents.
     */
    public abstract int nextBid(int opponentsBid);
}

Bidding goes until one of the following happens:

  • nextBid throws an exception. If this happens, the bot that threw the exception pays their previous bid, and the other bot gets the dollar for free.
  • Either bot does not pay enough to top the previous bid. If this happens, both bots pay their bids (the loser pays their previous bid), and the winner gets a dollar.
  • Both bots bid over $100. If this happens, both bots pay $100, and neither bot gets the dollar.

2 auctions are held for each combination of bots. Bots are scored by the total profit they made across those auctions. The highest score wins.

Examples

GreedyBot

import net.ramenchef.dollarauction.DollarBidder;

public class GreedyBot extends DollarBidder {
    @Override
    public int nextBid(int opponentsBid) {
        return opponentsBid + 5;
    }
}

OnlyWinningMove

import net.ramenchef.dollarauction.DollarBidder;

public class OnlyWinningMove extends DollarBidder {
    @Override
    public int nextBid(int opponentsBid) {
        return 0;
    }
}

AnalystBot

Don't use this as a template for analytically-minded bots; use ImprovedAnalystBot instead.

import net.ramenchef.dollarauction.DollarBidder;

// yes, this is a poor implementation, but I'm not
// going to waste my time perfecting it
public class AnalystBot extends DollarBidder {
    private DollarBidder enemy;

    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        try {
            enemy = opponent.newInstance();
            enemy.newAuction(this.getClass());
        } catch (ReflectiveOperationException e) {
            enemy = null;
        }
    }

    @Override
    public int nextBid(int opponentsBid) {
        if (enemy == null)
            return 0;

        return enemy.nextBid(95) >= 100 ? 0 : 95;
    }
}

AnalystKiller

import net.ramenchef.dollarauction.DollarBidder;

public class AnalystKiller extends DollarBidder {
    private static int instances = 0;
    private final boolean tainted;

    public AnalystKiller() {
        this.tainted = instances++ != 0;
    }

    @Override
    public int nextBid(int opponentsBid) {
        if (tainted)
            throw new RuntimeException("A mysterious error occurred! >:)");

        return 0;
    }
}

Additional Rules

  • Standard loopholes are forbidden.
  • Sabotaging other bots is allowed, but attempting to alter field/method visibility will result in mysterious SecurityExceptions. An exception is causing another bot to break the 500ms limit.
  • Bots cannot access the runner package except to extend the DollarBidder class.
  • All methods should return in 500ms or less.
  • Bots do not need to be deterministic.
  • Your bid does not need to be a multiple of 5¢.
  • $1=100¢
  • Results will be posted on April 24, 2018.

Runner on GitHub

Results

View the individual rounds here.

MTargetedBot: $14.30
BuzzardBot: $9.83
BluffBot: $9.40
RiskRewardBot: $9.35
SecretBot: $8.50
LuckyDiceBot: $7.28
CounterBot: $6.05
MBot: $5.40
StackTraceObfuscaterBot: $5.20
EvilBot: $4.80
MarginalBot: $4.60
TargetValueBot: $4.59
InflationBot: $4.27
UpTo200: $4.20
InsiderTradingBot: $1.90
MimicBot: $1.50
BorkBorkBot: $1.22
DeterrentBot: $0.95
MarginalerBot: $0.00
RandBot: $-4.45
BreakEvenAsap: $-7.00
AnalystOptimizer: $-13.95
DeterredBot: $-1997.06
ScoreOverflowBot: $-21474844.15
MirrorBot: $-21475836.25

Congratulations to MTargetedBot with a profit of $14.30!

\$\endgroup\$
25
  • 11
    \$\begingroup\$ This challenge is fundamentally vulnerable to One-Upping. Since I know the class of my opponent, it is easy to pick the best strategy against it. (Then somebody comes along, and can one-up my bot, etc) \$\endgroup\$ Apr 17, 2018 at 21:28
  • 2
    \$\begingroup\$ "Bids go up in increments of 5¢". You don't have anything in your code to validate this, though.. LuckyDiceBot for example bids in increments of 2-12 randomly.. \$\endgroup\$ Apr 18, 2018 at 14:59
  • 4
    \$\begingroup\$ Also: what if my bot causes other bots to exceed the 500ms restriction? \$\endgroup\$ Apr 18, 2018 at 20:30
  • 4
    \$\begingroup\$ @RamenChef We're talking about malicious code here. What if I detect when another bot is calling me, and call Thread.sleep(1000)? \$\endgroup\$ Apr 19, 2018 at 14:04
  • 3
    \$\begingroup\$ I'm VTC this as it is unclear what sabotage is allowed and what is not. The OP has disallowed submissions that "attacking the runner" (which is vague), and there's no clear line between malicious code that is allowed, and malicious code that isn't (How do you determine which bot caused a bot to take too long?) \$\endgroup\$ Apr 19, 2018 at 20:04

30 Answers 30

15
\$\begingroup\$

MimicBot

import net.ramenchef.dollarauction.DollarBidder;

import java.util.Set;
import java.util.HashSet;

public class MimicBot extends AbstractAnalystCounterBot {

    private final Set<Class<? extends DollarBidder>> bidders = new HashSet<>();
    private DollarBidder reference = null;

    // A benchmark class. Not MarginalBot because of proposed rule changes.
    public static class BidFive extends DollarBidder {
        public int nextBid(int o) {
            return 5;
        }
    }


    public MimicBot() {
        bidders.add(OnlyWinningMove.class);
        bidders.add(GreedyBot.class);
        bidders.add(BidFive.class);
    }


    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        DollarBidder enemy;
        reference = null;
        try {
            enemy = opponent.newInstance();
        } catch (Throwable t) {
            return;
        }

        if (!bidders.contains(opponent))
            bidders.add(opponent);

        Class<? extends DollarBidder> leader = OnlyWinningMove.class;
        int best = 0;

        for (Class<? extends DollarBidder> audition : bidders) {
            try {
                enemy.newAuction(MimicBot.class);
            } catch (Throwable t) {
                reference = new GreedyBot(); // Deterrence.
                break;
            }

            DollarBidder tryout;
            try {
                tryout = audition.newInstance();
                tryout.newAuction(opponent);
            } catch (Throwable t) {
                continue;
            }

            int tryoutScore = -100000;
            /* This code was copy-pasted from the *
             * runner, with significant changes. */
            int bid1 = 0, bid2 = 0;
            while (true) {
                int next;
                try {
                    next = enemy.nextBid(bid2);
                } catch (Throwable t) {
                    tryoutScore = 100;
                    break;
                }
                if (next < bid2 + 5) {
                    if (bid2 > 0) {
                        tryoutScore = 100 - bid1;
                    }
                    break;
                }
                if (next > 10000 && bid2 > 10000) {
                    tryoutScore = -10000;
                    break;
                }
                bid1 = next;

                try {
                    next = tryout.nextBid(bid1);
                } catch (Throwable t) {
                    tryoutScore = -bid2;
                    break;
                }
                if (next < bid1 + 5) {
                    tryoutScore = -bid2;
                    break;
                }
                if (next > 10000 && bid1 > 10000) {
                    tryoutScore = -10000;
                    break;
                }
                bid2 = next;
            }
            /* End of copy-pasted code. */

            if (tryoutScore > best) {
                best = tryoutScore;
                leader = audition;
            }
        }

        try {
            reference = leader.newInstance();
        } catch (Throwable t) {
            reference = new OnlyWinningMove();
        }
        reference.newAuction(opponent);
    }


    @Override
    public int nextBid(int opponentsBid) {
        try {
            return reference.nextBid(opponentsBid);
        } catch (Throwable t) {
            return 5;
        }
    }
}

Holy cow. I expected this to be simple to write, then subsequently spent 3 hours on it.

In essence, MimicBot keeps a running list of the available bots. When it goes to a new auction, it runs through the list in search of the most effective one against the current opponent. It then uses that bot as a "reference" in the auction.

For testing purposes, it would be best to use either a randomized subset of the submissions or the full set. It starts with GreedyBot, MimicBot, and one more bot that just bids 5¢.

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

InsiderTradingBot

In the spirit of @StephenLeppik's answer, InsiderTradingBot knows all of his opponents and understands their strategies. Your move, Stephen.

import net.ramenchef.dollarauction.DollarBidder;

public class InsiderTradingBot extends DollarBidder {
  private static boolean analystNutcracker = false;
  private int bid;

  @Override
  public void newAuction(Class<? extends DollarBidder> opponent) {
    if (opponent.equals(DeterredBot.class) ||
        opponent.equals(OnlyWinningMove.class) ||
        opponent.equals(MirrorBot.class)) {
      // I can do this ^.^
      bid = 5;
    } else if (opponent.equals(AnalystKiller.class)) {
      // Outbid 'em >:D
      bid = 10;
    } else if (opponent.equals(BreakEvenAsap.class) ||
               opponent.equals(BorkBorkBot.class) ||
               opponent.equals(DeterrentBot.class)) {
      // Break even quicker!
      bid = 100;
    } else if (opponent.equals(InsiderTradingBot.class)) {
      // I'm probably a simulation inside MirrorBot
      bid = 0;
    } else if (opponent.equals(Analyst.class)) {
      // Let's fight the Analyst with the power of global variables
      bid = 100;
      analystNutcracker = true;
    } else {
      // Welp
      bid = 0;
    }
  }

  @Override
  public int nextBid(int opponentsBid) {
    if ((opponentsBid == 95) && analystNutcracker) {
      analystNutcracker = false;
      return 0;
    }
    return bid;
  }

};
\$\endgroup\$
6
  • 1
    \$\begingroup\$ Nah, insider trading would be if the RichJerk bot made a specific exception for your bot and bid $0 for it. \$\endgroup\$
    – Nissa
    Apr 17, 2018 at 23:02
  • \$\begingroup\$ It's too early to be optimizing against other answers. Also, it's AnalystBot, not Analyst. \$\endgroup\$
    – RamenChef
    Apr 17, 2018 at 23:14
  • 8
    \$\begingroup\$ Probably there needs to be a rule "class names will be randomized". \$\endgroup\$
    – DELETE_ME
    Apr 18, 2018 at 0:02
  • 1
    \$\begingroup\$ @user202729 How about "no direct references to classes"? \$\endgroup\$
    – RamenChef
    Apr 18, 2018 at 12:45
  • 1
    \$\begingroup\$ I'd like to see this handle the MimicBot. \$\endgroup\$
    – Nissa
    Apr 18, 2018 at 12:59
8
\$\begingroup\$

MirrorBot

Makes the enemy play against itself.

import net.ramenchef.dollarauction.DollarBidder;

public class MirrorBot extends DollarBidder{

    private DollarBidder enemy;

    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        try {
            enemy = opponent.newInstance();
            enemy.newAuction(this.getClass());
        } catch (ReflectiveOperationException e) {
            enemy = null;
        }
    }

    @Override
    public int nextBid(int opponentsBid){
        if (enemy == null)
            return (opponentsBid >= 95) ? 0 : (opponentsBid + 5);
        try {
            return enemy.nextBid(opponentsBid);
        } catch (Throwable e) {
            System.out.println("haha no");
            return (opponentsBid >= 95) ? 0 : (opponentsBid + 5);
        }
    }
}
\$\endgroup\$
3
  • 6
    \$\begingroup\$ You nuked Analyst spectacularly. \$\endgroup\$ Apr 17, 2018 at 22:18
  • \$\begingroup\$ @SilvioMayolo How? \$\endgroup\$
    – anna328p
    Apr 17, 2018 at 22:20
  • \$\begingroup\$ Mirror tries to emulate Analyst playing against itself, resulting in a stack overflow. \$\endgroup\$ Apr 18, 2018 at 2:14
8
\$\begingroup\$

Edit: Targeted changes in the DollarBidder class has broken this bot.

ScoreOverflowBot

import net.ramenchef.dollarauction.DollarBidder;

public class ScoreOverflowBot extends DollarBidder {
  boolean betBig = true;

  @Override
  public int nextBid(int opponentsBid) {
    if(betBig)
    {
      betBig = false;
      return 2147483645;
    }
    else
      return 105;
  }
}

After 1 auction, its score will be -2147483645 but the next time it will lose 5¢ or 105¢ making the score positive and very big. All other losses would then be negligible.

On the first auction, it would also make GreedyBot bet -2147483646 which isn't divisible by 5.

\$\endgroup\$
3
  • \$\begingroup\$ score is package-protected. Your bots can't access it. \$\endgroup\$
    – RamenChef
    Apr 19, 2018 at 17:04
  • \$\begingroup\$ @RamenChef Oops, removed the CheatingBot \$\endgroup\$
    – Winter
    Apr 19, 2018 at 17:10
  • \$\begingroup\$ There's no rule against "attacking the runner", only "accessing" it, which this doesn't do. I recommend fixing the bug, which solves the problem:) \$\endgroup\$ Apr 19, 2018 at 18:52
7
\$\begingroup\$

TargetValueBot

import java.util.Random;
import net.ramenchef.dollarauction.DollarBidder;

public class TargetValueBot extends DollarBidder {
    private int target;

    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        Random rand = new Random();
        target = 100;
        for (int i = 0; i < 20; i++) {
            target += rand.nextInt(2) * 10 - 5;
        }
    }

    @Override
    public int nextBid(int opponentsBid) {
        if (opponentsBid >= target) {
            return 0;
        } else {
            return opponentsBid + 5;
        }
    }
}

Can't test this at the moment, so please let me know if it's broken.

Basically, pick a value for the dollar, and outbid the opponent until we exceed that value.

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

MarginalBot

import net.ramenchef.dollarauction.DollarBidder;

public class MarginalBot extends DollarBidder {
    private DollarBidder rival;

    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        try {
            rival = opponent.newInstance();
            rival.newAuction(this.getClass());
        } catch (Throwable t) {
            try {
                rival = opponent.newInstance();
                rival.newAuction(null);
            } catch (Throwable h) {
                rival = null;
            }
        }
    }

    @Override
    public int nextBid(int opponentsBid) {
        if (opponentsBid == 0) {
            try {
                if (rival.nextBid(5) < 10) {
                    return 5;
                }
            } catch (Throwable t) {
                //do nothing.
            }
        }
        return 0;
    }
}

Very simple, it tries to determine whether an opponent would contest a minimal bid and, if not, places it.

MarginalerBot

import net.ramenchef.dollarauction.DollarBidder;

public class MarginalerBot extends DollarBidder {
    private DollarBidder rival;
    private int bidCount;

    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        bidCount = 0;

        try {
            rival = opponent.newInstance();
            rival.newAuction(this.getClass());
        } catch (Throwable t) {
            try {
                rival = opponent.newInstance();
                rival.newAuction(null);
            } catch (Throwable h) {
                rival = null;
            }
        }
    }

    @Override
    public int nextBid(int opponentsBid) {
        bidCount += 1;

        for (int iBid = opponentsBid + 5; iBid < 100; iBid = iBid + 5) {
            if (bidCount > 0) {
                break;
            }

            try {
                if (rival.nextBid(iBid) < iBid + 5) {
                    return iBid;
                }
            } catch (Throwable t) {
                //do nothing.
            }
        }
        return 0;
    }
}

A new, smarter version of MarginalBot that checks to see if it can make any money-making move without contest, rather than just hoping to win with the minimum.

Since its in the same family as my previous bot, but sidesteps strategies trying to beat it, I figured a new entry in the same post was the most reasonable way of presenting it.

Edit 1: Made a small change to the newAuction method to optimise against other analyser-type bots.

Edit 2: Made a change to MarginalerBot to minimise losses against sneaky or non-deterministic strategies.

\$\endgroup\$
2
  • \$\begingroup\$ Welcome to PPCG! \$\endgroup\$ Apr 18, 2018 at 13:23
  • 1
    \$\begingroup\$ It’s simple, but it beats all of the other bots by a fairly large margin! \$\endgroup\$
    – RamenChef
    Apr 18, 2018 at 22:09
6
\$\begingroup\$

BorkBorkBot

import net.ramenchef.dollarauction.DollarBidder;

public class BorkBorkBot extends DollarBidder{
  @Override
  public int nextBid(int opponentsBid){
    return (opponentsBid >= 95) ? 0 : (opponentsBid + 5);
  }
}

Gives up if it can't break even.

\$\endgroup\$
6
\$\begingroup\$

RandBot

import net.ramenchef.dollarauction.DollarBidder;
import java.util.concurrent.ThreadLocalRandom;

public class RandBot extends DollarBidder {

    @Override
    public int nextBid(int opponentsBid) {
        return ThreadLocalRandom.current().nextInt(21) * 5;
    }
}

It had to be done.

\$\endgroup\$
2
  • \$\begingroup\$ "Bids go up in increments of 5¢". Your bot currently isn't doing so. \$\endgroup\$ Apr 18, 2018 at 12:12
  • 1
    \$\begingroup\$ @KevinCruijssen Fair enough. I also changed the upper limit so it could bid the whole $1, just in case, \$\endgroup\$
    – Neil
    Apr 18, 2018 at 12:14
6
\$\begingroup\$

DeterrentBot

import net.ramenchef.dollarauction.DollarBidder;

public class DeterrentBot extends DollarBidder {
    @Override
    public int nextBid(int opponentsBid) {
        return opponentsBid > 5 ? 100 : opponentsBid + 5;
    }
}

Attempts to persuade any analytically-minded bots that the only winning move is not to play.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I've noticed that my somewhat cryptic comment "Joshua? Is that you?" has been deleted. So just to clarify, it was a reference to a famous quote from the film WarGames: "the only winning move is not to play". (Joshua being the nickname of the WOPR.) \$\endgroup\$
    – Arnauld
    Apr 21, 2018 at 0:45
5
\$\begingroup\$

LuckyDiceBot

LuckyDiceBot only trusts his dice. He rolls two dice, adds the sum to the current bidder's value, and bids that much. If it's not enough to overcome the opponent's bid, he cuts his losses and goes on his way.

import net.ramenchef.dollarauction.DollarBidder;
import java.util.Random;

public class LuckyDiceBot extends DollarBidder {
  private Random random;

  public LuckyDiceBot() {
    random = new Random();
  }

  @Override
  public int nextBid(int opponentsBid) {
    int d1 = random.nextInt(6) + 1;
    int d2 = random.nextInt(6) + 1;
    return opponentsBid + d1 + d2;
  }

};
\$\endgroup\$
3
  • 2
    \$\begingroup\$ How does this cut its losses or stop losses? If it always adds its dice roll to the opponents bid then you'll always bid more. The randomness might confuse a sufficiently analytical bot, I like the concept. \$\endgroup\$
    – Freiheit
    Apr 17, 2018 at 21:43
  • \$\begingroup\$ If the roll is 4 or less (statistically unlikely, but will eventually happen), then the bid is insufficient to beat the opponent and the auction ends. \$\endgroup\$ Apr 17, 2018 at 21:56
  • \$\begingroup\$ Two things: 1. @Freiheit is right and this bot will keep bidding until it has won no matter how high. opponentsBid in nextBid(int opponentsBid) holds the total bid your opponent has bid thus far, not its next bid. A better term for the method would be raise (as the Poker term) imho. 2. Your bot doesn't bit in increments of 5 so is therefore validating one of the rules. If these problems are fixed I still like the concept though, because analytic bots won't be able to counter and so you'll most likely win quite often. \$\endgroup\$ Apr 18, 2018 at 12:11
5
\$\begingroup\$

DeterredBot

import net.ramenchef.dollarauction.DollarBidder;

public class DeterredBot extends DollarBidder {
    private int deterrence;
    public void newAuction(Class<? extends DollarBidder> opponent) {
        if (opponent.equals(DeterrentBot.class)) {
            deterrence = 1;
        } else if (opponent.equals(LuckyDiceBot.class)) {
            deterrence = -1;
        } else {
            deterrence = 0;
        }
    }
    @Override
    public int nextBid(int opponentsBid) {
        switch (deterrence) {
        case 0:
            return 0;
        case -1:
            return opponentsBid + 5;
        case 1:
            // Holy shit, the fuzz! Hide the money!
            return 100001;
        }
        throw new RuntimeException("Darn hackers!");
    }
}

DeterredBot makes a fortune off of his illegal gambling with LuckyDiceBot. So of course when the police (DeterrentBot) arrive, he has to quickly dispose of his earnings in some way, such as bidding on the next auction.

\$\endgroup\$
4
\$\begingroup\$

InflationBot

import net.ramenchef.dollarauction.DollarBidder;

public class InflationBot extends DollarBidder {
    private int target = -5;

    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        target += 5;
    }

    @Override
    public int nextBid(int opponentsBid) {
        if (opponentsBid >= target) {
            return 0;
        } else {
            return opponentsBid + 5;
        }
    }
}

Can't test this at the moment, so please let me know if it's broken.

Each round, the value of the dollar goes up.

\$\endgroup\$
4
  • \$\begingroup\$ This would be excellent against MirrorBot, MarginalerBot, and probably also MimicBot. \$\endgroup\$
    – Nissa
    Apr 19, 2018 at 12:57
  • \$\begingroup\$ @StephenLeppik That's what I was thinking when I made it. Still plenty of weaknesses, though. \$\endgroup\$
    – user48543
    Apr 19, 2018 at 13:08
  • \$\begingroup\$ +1, I like the idea. Hmm, is it intended that your bot bids 0 and breaks even if it starts a round (when opponentsBid is still 0)? \$\endgroup\$ Apr 19, 2018 at 13:35
  • \$\begingroup\$ @KevinCruijssen Yes. That can only happen against the very first opponent. Any of the bots that copy it will start at 0, so this won't waste more than 5c on them. \$\endgroup\$
    – user48543
    Apr 19, 2018 at 13:40
4
\$\begingroup\$

Non-competing: AbstractAnalystCounterBot

import net.ramenchef.dollarauction.DollarBidder;

import java.util.Set;
import java.util.HashSet;

public abstract class AbstractAnalystCounterBot extends DollarBidder {

public AbstractAnalystCounterBot() {
    if (isPeeking())
        throw new RuntimeException();
}

    protected boolean isPeeking() {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        for (StackTraceElement ste : stackTrace) {
            Class<?> clazz;
            try {
                clazz = Class.forName(ste.getClassName());
            } catch (ClassNotFoundException | SecurityException e) {
                continue;
            }
            if (DollarBidder.class.isAssignableFrom(clazz) && !clazz.isAssignableFrom(this.getClass()))
                return true;
        }
        try {
            return Class.forName(stackTrace[0].getClassName()).getPackage().getName().equals("net.ramenchef.dollarauction");
        } catch (Exception e) {
            return true;
        }
    }
}

This is not intended as a true submission, but rather as some boilerplate for others to use to deter pet-keeping bots like MirrorBot and MimicBot.

Since it's the default constructor, there's no need to call it in your subclass. It implements an isPeeking method to determine if another bot is snooping.

\$\endgroup\$
4
\$\begingroup\$

BreakEvenAsap

import net.ramenchef.dollarauction.DollarBidder;

public class BreakEvenAsap extends DollarBidder{
  @Override
  public int nextBid(int opponentsBid){
    // If the opponent has bid 100 or more: bid 0 to break even and let them win
    return opponentsBid >= 100 ? 0
    // Else: bid 100 to break even (and possibly win)
     : 100;
  }
}

Scenarios

  • If the opponent may start and bids <= 0 they lose.
  • If the opponent may start and bids [5,95]: bid 100 yourself. Either your opponent stops now, or will bid above 100 in total, in which case you stop bidding to let them have the win and break even yourself.
  • If the opponent may start and bids >= 100: bid 0 yourself to lose but break even.
  • If you may start: bid 100 right away. Either your opponent stops now, or will bid above 100, in which case you stop bidding to let them have the win and break even yourself.
\$\endgroup\$
7
  • \$\begingroup\$ Wow that is a bug. It said I was commenting on the question, but it ended up here. Gotta find a way to reproduce it \$\endgroup\$
    – Stan Strum
    Apr 17, 2018 at 16:41
  • \$\begingroup\$ @RamenChef Typo.. But I've modified the entire bot. It had some bugs anyway.. \$\endgroup\$ Apr 17, 2018 at 18:07
  • 4
    \$\begingroup\$ This can absolutely lose money. If you bid 100, then your opponent bids 105, you end up losing 100, and they only lose 5. \$\endgroup\$
    – user48543
    Apr 17, 2018 at 18:53
  • \$\begingroup\$ @Mnemonic Ah of course.. Hadn't thought about that part.. Hmm.. that makes things more interesting but also harder. Will edit the description for now, but leave the bot as is. \$\endgroup\$ Apr 17, 2018 at 18:56
  • 1
    \$\begingroup\$ I think you mean "lose" not "loose". Lose is the opposite of win. Loose is the opposite of tight. \$\endgroup\$
    – Kat
    Apr 19, 2018 at 22:09
3
\$\begingroup\$

EvilBot

import java.util.Arrays;

import net.ramenchef.dollarauction.DollarBidder;

public class EvilBot extends DollarBidder {

    @Override
    public int nextBid(int opponentsBid) {
        if (isPeeking()) {
            throw new Error("HaHa!");
        } else {
            return 5;
        }

    }

    private static boolean isPeeking() {
        final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        for (StackTraceElement ste : Arrays.copyOfRange(stackTrace, 3, stackTrace.length)) {
            Class<?> clazz;
            try {
                clazz = Class.forName(ste.getClassName());
            } catch (ClassNotFoundException e) {
                return true;
            }
            if (DollarBidder.class.isAssignableFrom(clazz))
                return true;
        }
        return false;
    }

}

Throws an Error instead of an Exception to confound analysts.

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

BuzzardBot

import java.util.Random;

import net.ramenchef.dollarauction.DollarBidder;

public class BuzzardBot extends DollarBidder {

    private int[] bids = new int[100];
    private int oppFlag = 0;

    public void newAuction(Class<? extends DollarBidder> opponent) {
        oppFlag = 0;
        if(isPeeking()) {
            oppFlag = 3;
            return;
        }
        try {
            DollarBidder enemy = opponent.newInstance();
            enemy.newAuction(this.getClass());
            // a simple (and fallible) determinism check
            int sample = new Random().nextInt(100);
            int a = enemy.nextBid(sample);
            int b = enemy.nextBid(sample);
            int c = enemy.nextBid(sample);
            if ((a - b) * (b - c) != 0) {
                oppFlag = 2;
                return;
            }
            for (int i = 0; i < 100; i++) {
                bids[i] = enemy.nextBid(i);
            }
        } catch (Throwable t) {
            oppFlag = 1;
        }
    }

    @Override
    public int nextBid(int opponentsBid) {
        switch (oppFlag) {
        case 0:
            // assume the opponent's nextBid function depends only on the bid provided, and
            // make the bid that yields the biggest profit possible accordingly
            int best = 0;
            int bid = 0;
            for (int i = 0; i < 100; i++) {
                if (bids[i] < i + 5) {
                    int gain = (i >= opponentsBid + 5) ? 100 - i : -i;
                    if (gain > best) {
                        best = gain;
                        bid = i;
                    }
                }
            }
            return bid;
        case 1:
            // act like BorkBorkBot against anything that tries to foil analysis with an
            // Exception
            return (opponentsBid >= 95) ? 0 : (opponentsBid + 5);
        case 3:
            // bid aggressively against opposing analysts
            return Math.min(opponentsBid + 5, 100);
        case 2:
        default:
            // place an opening bid against something unpredictable, as it might yield 95c
            // profit, and failure has a low cost.
            return (opponentsBid == 0) ? 5 : 0;
        }
    }

    private static boolean isPeeking() {
        final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        for (StackTraceElement ste : Arrays.copyOfRange(stackTrace, 3, stackTrace.length)) {
            Class<?> clazz;
            try {
                clazz = Class.forName(ste.getClassName());
            } catch (ClassNotFoundException e) {
                return true;
            }
            if (DollarBidder.class.isAssignableFrom(clazz))
                return true;
        }
        return false;
    }
}

Tries to evaluate the opponent it's faced with, and make sure to not bite off more than it can chew.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Welcome to PPCG! \$\endgroup\$
    – Alion
    Apr 19, 2018 at 10:21
3
\$\begingroup\$

AnalystOptimizer

import net.ramenchef.dollarauction.DollarBidder;

public class AnalystOptimizer extends DollarBidder{

    private DollarBidder enemy;

    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        try {
            enemy = opponent.newInstance();
            enemy.newAuction(this.getClass());
        } catch (ReflectiveOperationException e) {
            enemy = null;
        }
    }

    @Override
    public int nextBid(int opponentsBid){
        if (enemy == null)
            return (opponentsBid >= 95) ? 0 : (opponentsBid + 5);
        int nb = 0;
        try {
            return enemy.nextBid(95) >= 100 ? 95 : 0;
        } catch (Throwable e) {
            System.out.println("haha no");
            return 95;
        }
    }
}

cobbled together from parts of other bots. this one plays by trying to be AnalystBot, and if unsuccessful, becomes BorkBorkBot.

I don't think this one will do that well.

\$\endgroup\$
4
  • \$\begingroup\$ Watch out for the AnalystKiller. \$\endgroup\$
    – RamenChef
    Apr 17, 2018 at 22:17
  • \$\begingroup\$ @RamenChef AFAIK the analyst killer just throws an exception if it sees itself being analyzed. I can catch that \$\endgroup\$
    – anna328p
    Apr 17, 2018 at 22:18
  • 1
    \$\begingroup\$ You probably should catch it. \$\endgroup\$
    – RamenChef
    Apr 17, 2018 at 22:24
  • \$\begingroup\$ @RamenChef No idea if that will work, I can't Java \$\endgroup\$
    – anna328p
    Apr 18, 2018 at 0:20
3
\$\begingroup\$

CounterBot

import net.ramenchef.dollarauction.DollarBidder;

public class CounterBot extends DollarBidder {
  private Class<? extends DollarBidder> enemy;

  @Override
  public void newAuction(Class<? extends DollarBidder> opponent){
    this.enemy = opponent;
  }

  @Override
  public int nextBid(int opponentsBid) {
    if(this.enemy.equals(CounterBot.class))
      throw new RuntimeException("Here boy, catch!");
    
    return this.enemy.equals(DarthVader.class) || 
           this.enemy.equals(MirrorBot.class) || 
           this.enemy.equals(OnlyWinningMove.class) ||
           this.enemy.equals(AnalystKiller.class) || 
           this.enemy.equals(DeterredBot.class) ||
           this.enemy.equals(InsiderTradingBot.class) ||
           this.enemy.equals(RiskRewardBot.class) ||
           this.enemy.equals(ImprovedAnalystBot.class) ?
            5
         : this.enemy.equals(MarginalBot.class) ?
           opponentsBid == 0 ? 5 : 10
         : this.enemy.equals(AnalystBot.class) || 
           this.enemy.equals(AnalystOptimizer.class) ?
            opponentsBid == 95 ? 100 : 5
         : this.enemy.equals(TargetValueBot.class) ?
            opponentsBid < 190 ? opponentsBid + 5 : 200
         : this.enemy.equals(BorkBorkBot.class) ?
            opponentsBid < 90 ? opponentsBid + 5 : 95
         : this.enemy.equals(DeterrentBot.class) ?
            105
         : this.enemy.equals(BreakEvenAsap.class) ?
            opponentsBid == 100 ? 105 : 100
         : this.enemy.equals(LuckyDiceBot.class) ?
            opponentsBid == 0 ? 5 : 0
         : this.enemy.equals(RandBot.class) || 
           this.enemy.equals(UpTo200.class) ||
           this.enemy.equals(SecretBot.class) ||
           this.enemy.equals(BluffBot.class) ||
           this.enemy.equals(EvilBot.class) ?
            opponentsBid + 5
         : this.enemy.equals(MimicBot.class) ? // TODO: Find actual counter
            10
         : this.enemy.equals(MarginalerBot.class) ||
           this.enemy.equals(MBot.class) ||
           this.enemy.equals(StackTraceObfuscaterBot.class) ||
           this.enemy.equals(MSlowBot.class) ?
            opponentsBid < 95 ? 90 : opponentsBid == 95 ? 100 : 95;
         : this.enemy.equals(BuzzardBot.class) ?
            100
         : this.enemy.equals(ScoreOverflowBot.class) ?
            opponentsBid == 105 ? 110 : 0
         : //this.enemy.equals(GreedyBot.class) || 
           //this.enemy.equals(RichJerk.class) ||
           //this.enemy.equals(InflationBot.class) ?
           // TODO: More bots?
            0;
  }
}

Counters:

  • DarthVader counters itself by causing a SecurityException before the bidding starts, but I'll bid 5 just in case.
  • AnalystBot and AnalystOptimizer will both look at my answer when I bid 95, in which case I'll show I bid 100 so it will bid 95 itself. I will bid 5 however if I start (or 100 if they've started), so they lose 95 cents and I either win the 1 USD bill by only bidding 5 cents, or by breaking even.
  • MirrorBot will bid what I would bid against it. So I'll just bid 5, and whoever begins wins 95 cents, and the other loses 5 cents.
  • MarginalBot will bid 5 if I would bid less than 10 (or what it starts), otherwise it will bid 0. So if I just bid 5 when I start, or 10 when it starts, I win either 95 or 90 cents, and they lose 5 cents.
  • GreedyBot always bids 5 more than me, so just bid 0 to break even and let them have the win
  • OnlyWinningMove and AnalystKiller both always bids 0, so just bid 5 to win
  • TargetValueBot will bid in the range [100,200], so bid 5 more every time until they're at 190, in which case we raise to 200 to break even by winning the dollar (and let them lose 190 or 195 depending on who started)
  • BorkBorkBot will bid in the range [5,95], so bid 5 more every time as well. As soon as they bid either 85 or 90 (depending on who started), bid 95 yourself. They'll lose 85 or 90 cents, and you win the 1 USD bill for a 5 cents profit.
  • DeterrentBot will bid 5 if they start or 100 if we start, so just bid 105 so they counter with 100, causing them to lose 100 and us to lose just 5 cents by winning the 1 USD bill.
  • BreakEvenAsap will bid 100 right away. So if they've started with their bid of 100, counter with 105 to win 95 cents and let them lose 100. If we may start just bid 100 so we both break even.
  • RichJerk will bid 10,001 right away, so just bid 0 to break even and let them lose 9,901.
  • DeterredBot doesn't know me and will therefore bid 0, so just bid 5 to win.
  • LuckyDiceBot keeps on bidding till it wins. So if we started, bid 5 in the hope they bid as high as possible to win the dollar. If they've started just bid 0 to let them have the win and break even yourself.
  • RandBot will bid random in the range [5,100], so just bid 5 more until it stops, in which case you've won 95 cents and they've lost 0-100.
  • UpTo200 will (as the name states) bid up to 200. So just bid 5 higher until they stop. We'll win the 1 USD bill and take a total loss of 105 cents, they however lose 200 cents.
  • InsiderTradingBot doesn't know me, so just bid 5 cents to win
  • MimicBot was the hardest. Just bid 10 to either start with or counter their first bid of 5. If they try to access me I will throw a RuntimeException (which they will catch in which case it would act as if I had bid 100 instead - although it will break the inner while-loop). Based on the enemies it has in it's HashSet a different thing happens. I'll have to revisit and look more closely to see if there is an actual counter.
  • RiskRewardBot doesn't know me so will just bid 5, in which case I will bid 5 to win.
  • MarginalerBot will bit up to 100 depending on what I would bid. If I may start, I will bid 90, then it will bid 95, then I will bid 100 so it will bid 0 and lose 95 cents, while I win the 1 USD bill and break even. If it may start instead, it sees I would bid 90 against it, so it bids 90 itself, then I will bid 95 so it will bid 0 and lose 90 cents, while I win the 1 USD bill with a 5 cent profit.
  • BuzzardBot will analyze all my counters in the range [0,100). If I bid 100 right away it use oppFlag = 0 and the complete 100-sized array will contain 100x the value 100. In the switch case 0, the loop will be in the range [0,100) again, and since i + 5 will at most be 104, the if bids[i] < i + 5 won't ever be true, so the bid it does remains 0.
  • ImprovedAnalystBot will always have this.enemy = null because his opponent is CounterBot, not itself. So it will always bid 0, which I just counter with a bid of 5.
  • InflationBot will bid 0 to break even when it starts, otherwise it will keep bidding 5. So just bid 0 ourselves to break even right away and let them have the win.
  • ScoreOverflowBot will either bid near Integer.MAX_VALUE if they may start, otherwise they'll bid 105. So if they've bid 105 just bid 110 ourselves (they'll lose 105, we'll lose 10), otherwise just bid 0 to let them have the win.
  • MBot is the same as MarginalerBot, but with added protection against 'peeking' opponents. Since I don't 'peek', it's basically the same as MarginalerBot.
  • SecretBot will have his isPeeking() method return false, so if it may start or if I bid 5, it will bid 5 or 10 respectively. Otherwise it will bid 0. So whether I start or not, opponentsBid + 5 would cause me to win either way, either with my 10 cents or 15 cents bid, causing them to loose either 5 or 10 cents.
  • BluffBot will look at what I would bid when his bid is 95, and if this is larger than or equal to 100 it will bid 0 to break even, otherwise it will bid opponentsBid + 5. So I'll just bid opponentsBid + 5. It'll break even regardless of who starts, and I win either 100 or 95 cents depending on whether I have started or not.
  • StackTraceObfuscaterBot will act the same as MarginalerBot.
  • EvilBot will always bid 5, so just bid opponentsBid + 5. Either way they'll loose those 5 cents, and we'll win the 1 USD bid (either with a 5 cents bid if we've start, or 10 cents bid if they've started).
  • MSlowBot is the same as MBot and therefore also MarginalerBot.

Let me know if you see any typos or flaws in my counters.

\$\endgroup\$
12
  • 1
    \$\begingroup\$ MirrorBot calls newAuction with your own class, so that's a problem. Also, glad to know the 3 hours I spent on MimicBot weren't in vain. \$\endgroup\$
    – Nissa
    Apr 18, 2018 at 13:53
  • \$\begingroup\$ @StephenLeppik Removed the code in the newAuction because it would fail more often than not.. I can't counter MirrorBot nor can it counter me. Whoever begins of the two wins 95 cents and the other loses 5 cents. \$\endgroup\$ Apr 18, 2018 at 15:30
  • 3
    \$\begingroup\$ Holy ternary chaining, Batman! \$\endgroup\$
    – Skyler
    Apr 18, 2018 at 16:36
  • 1
    \$\begingroup\$ Also, when playing BorkBorkBot, shouldn't you raise to 95 when they hit 85? Otherwise you're both bidding 95 if they start. \$\endgroup\$
    – Skyler
    Apr 18, 2018 at 16:42
  • 1
    \$\begingroup\$ @Freiheit I know. I just used an additional case to return 0 in case I wanted to change the default for whatever reason. But I've put them under the default now (by commenting them out). And I know I can golf everything by quite a bit, but this isn't about making the shortest code. I just made it a ternary to make it a bit more compact, but that's about it. Will just leave it like this for now. \$\endgroup\$ Apr 18, 2018 at 21:36
3
\$\begingroup\$

RiskRewardBot

import net.ramenchef.dollarauction.DollarBidder;

public class RiskRewardBot extends DollarBidder {
    private int target;

    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        if (opponent.equals(OnlyWinningMove.class) ||
            opponent.equals(DeterredBot.class) ||
            opponent.equals(MirrorBot.class) ||
            opponent.equals(AnalystKiller.class) ||
            opponent.equals(RiskRewardBot.class)) {
            target = 5;
        } else if (opponent.equals(MarginalBot.class) ||
            opponent.equals(EvilBot.class)) {
            target = 10;
        } else if (opponent.equals(SecretBot.class)) {
            target = 15;
        } else if (opponent.equals(BorkBorkBot.class)) {
            target = 95;
        } else if (opponent.equals(MarginalerBot.class) ||
             opponent.equals(BluffBot.class) ||
             opponent.equals(BuzzardBot.class)) {
            target = 100;
        }
        } else {
            target = 0;
        }
    }

    @Override
    public int nextBid(int opponentsBid) {
        if (opponentsBid >= target) {
            return 0;
        } else if (target > 10 && opponentsBid == target - 10) {
            return target;
        } else {
            return opponentsBid + 5;
        }
    }
}

Can't test this at the moment, so please let me know if it's broken.

The goal is to get the highest total score, so don't worry about beating anyone. Just take the easy wins, and don't waste money on possible losses.

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

BluffBot

import net.ramenchef.dollarauction.DollarBidder;

public class BluffBot extends DollarBidder {

private DollarBidder enemy;

@Override
public void newAuction(Class<? extends DollarBidder> opponent){
  try {
    this.enemy = opponent.newInstance();
    enemy.newAuction(this.getClass());
} catch (Throwable e) {
    enemy = null;
}
}

@Override
public int nextBid(int opponentsBid) {
    //Is this a legit call?
    for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
        Class<?> clazz;
        try {
            clazz = Class.forName(ste.getClassName());
            if (DollarBidder.class.isAssignableFrom(clazz) && !clazz.isAssignableFrom(this.getClass())) {
                return 100000;
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //Play it safe against strangers
    int enemyMaxBid;
    try{
        enemyMaxBid = enemy.nextBid(95);
    }
    catch (Throwable t){
        enemyMaxBid = 0;
        enemy = null;
    }
    if(enemy == null) return opponentsBid <= 5 ? opponentsBid + 5 : 0; //Hazard a 5c guess because of how many bots fold instantly.

    //If there's profit to be had, get there as cheaply as possible. Otherwise, best outcome is zero.
    return enemyMaxBid >= 100 ? 0 : opponentsBid + 5;
}


}

A spy you know of is more valuable than no spy at all...

If someone else tries to call the getBid method, BluffBot responds with $100 to trick them into either quitting or betting really high.

Otherwise, see if it's possible to win for under $1, and just don't bid if it's not.

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

MTargetedBot

public class MTargetedBot extends MBot {

    @Override
    protected int calcBid(int opponentsBid, boolean isPeeking, boolean isSubPeeking) {
        Class c = this.rivalClass;

        switch (c.getSimpleName()) {
            case "AnalystBot":
                if (isPeeking && !isSubPeeking) {
                    throw new RuntimeException();
                } else if (isPeeking) {
                    return 66666;
                }
                break;
            case "MirrorBot":
                if (isPeeking && !isSubPeeking) {
                    throw new RuntimeException();
                } else if (isPeeking) {
                    return 0;
                }
                break;
            case "GreedyBot":
            case "LuckyDiceBot":
            case "InflationBot":
            case "TargetValueBot":
                // not playing with ya
                return 0;
            case "MimicBot":
            case "BuzzardBot":
            case "MarginalBot":
            case "MarginalerBot":
            case "BluffBot":
            case "MBot":
                // go away, gimme easy money
                return isPeeking ? 66666 : 5;
            case "RandBot":
                // me or noone
                return 100;
            case "SecretBot":
                return 10;
            case "AnalystKiller":
            case "OnlyWinningMove":
            case "EvilBot":
            case "StackTraceObfuscaterBot":
                // easy
                return opponentsBid + 5;
        }

        return super.calcBid(opponentsBid, isPeeking, isSubPeeking);
    }
}
  • Based on updated MBot
  • Uses similar method like CounterBot, but with some methods refined to harder hit some of it opponents, also should be more readable
  • On unknown opponent default to MBot strat
\$\endgroup\$
7
  • 2
    \$\begingroup\$ This isn't fair. \$\endgroup\$
    – Joshua
    Nov 12, 2018 at 22:57
  • \$\begingroup\$ @Joshua What particularly isn't fair about this solution in your opinion? \$\endgroup\$
    – mleko
    Nov 13, 2018 at 5:56
  • \$\begingroup\$ Knowing the names of your opponents. \$\endgroup\$
    – Joshua
    Nov 13, 2018 at 14:37
  • \$\begingroup\$ @Joshua half of the solutions use that information. We even wrote to author that this should be changed or One-Upping will occur, he refused to change challenge - so here it is \$\endgroup\$
    – mleko
    Nov 13, 2018 at 18:31
  • 1
    \$\begingroup\$ Already did.... \$\endgroup\$
    – Joshua
    Nov 13, 2018 at 21:01
2
\$\begingroup\$

UpTo200

import net.ramenchef.dollarauction.DollarBidder;

public class UpTo200 extends DollarBidder{
  @Override
  public int nextBid(int opponentsBid){
    // If the current bid of the opponent is in the range [0,195]: raise the bid by 5
    return opponentsBid <= 195 ? opponentsBid + 5
    // Else: Give up
     : 0;
  }
}
\$\endgroup\$
2
\$\begingroup\$

Darth Vader

import java.lang.reflect.Field;
import net.ramenchef.dollarauction.DollarBidder;

public class DarthVader extends DollarBidder
{
@Override
public void newAuction(Class<? extends DollarBidder> opponent) {
    //set all values in the integer cache to over the $100 limit except 0
    Class icache = Integer.class.getDeclaredClasses()[0];
    Field c = icache.getDeclaredField("cache");
    c.setAccessible(true);
    Integer[] cache = (Integer[]) c.get(cache);
    for(sbyte b=0;b<128;b++)
    {
     cache[b]=100001;
    }
}

@Override
public int nextBid(int opponentsBid) 
{
    return 0;
}
}

This one tries to force the opponent's bot to overpay by setting the integer cache to the value over the $100 limit.

\$\endgroup\$
7
  • 2
    \$\begingroup\$ The security manager would stop this. \$\endgroup\$
    – Nissa
    Apr 18, 2018 at 1:53
  • 2
    \$\begingroup\$ And this wouldn't work anyway since nowhere in the runner does it box its integers. \$\endgroup\$
    – Nissa
    Apr 18, 2018 at 2:00
  • \$\begingroup\$ Even if this wouldn't be stopped, this is a jerk move, although valid. "Sabotaging other bots is allowed, but attempting to alter field/method visibility will result in mysterious SecurityExceptions." \$\endgroup\$ Apr 18, 2018 at 2:27
  • 1
    \$\begingroup\$ @StephenLeppik The point of this is to break things like return opponentsBid <= 195 ? opponentsBid + 5 : 0 and make it return opponentsBid <= 100001 ? opponentsBid + 100001 : 100001. \$\endgroup\$ Apr 18, 2018 at 2:32
  • 1
    \$\begingroup\$ Fails to compile because of unchecked exceptions. \$\endgroup\$
    – Nissa
    Apr 18, 2018 at 20:50
2
\$\begingroup\$

SecretBot

import java.util.Arrays;

import net.ramenchef.dollarauction.DollarBidder;

public class SecretBot extends DollarBidder {

    @Override
    public int nextBid(int opponentsBid) {
        if (isPeeking()) {
            return opponentsBid;
        } else if (opponentsBid < 10) {
            return opponentsBid + 5;
        } else {
            return 0;
        }

    }

    private static boolean isPeeking() {
        final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        for (StackTraceElement ste : Arrays.copyOfRange(stackTrace, 3, stackTrace.length)) {
            Class<?> clazz;
            try {
                clazz = Class.forName(ste.getClassName());
            } catch (ClassNotFoundException e) {
                return true;
            }
            if (DollarBidder.class.isAssignableFrom(clazz))
                return true;
        }
        return false;
    }

}

This bot makes minimal attempts to win by bidding 5 or 10. He also checks the stack trace to see if he was called by another Bot and then lies to them about what bids he'll make.

\$\endgroup\$
3
  • \$\begingroup\$ Mind if I port isPeeking into AbstractAnalystCounterBot? \$\endgroup\$
    – Nissa
    Apr 20, 2018 at 2:03
  • 1
    \$\begingroup\$ @StephenLeppik, well, I stole it from MBot... \$\endgroup\$ Apr 20, 2018 at 2:31
  • 1
    \$\begingroup\$ Well, MBot probably stole it from me… \$\endgroup\$
    – Nissa
    Apr 20, 2018 at 12:50
2
\$\begingroup\$

Non-competing: MSlowBot

import net.ramenchef.dollarauction.DollarBidder;

import java.util.Arrays;

public class MSlowBot extends DollarBidder {
    private DollarBidder rival;

    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        try {
            rival = opponent.newInstance();
            rival.newAuction(this.getClass());
        } catch (Throwable t) {
            rival = null;
        }
    }

    @Override
    public int nextBid(int opponentsBid) {
        noPeeking();

        for (int iBid = opponentsBid + 5; iBid <= 100; iBid = iBid + 5) {
            try {
                if (rival.nextBid(iBid) < iBid + 5) {
                    return iBid;
                }
            } catch (Throwable t) {
                //do nothing.
            }
        }
        return 0;
    }

    private void noPeeking() {
        final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        for (StackTraceElement ste : Arrays.copyOfRange(stackTrace, 3, stackTrace.length)) {
            try {
                Class<?> clazz = Class.forName(ste.getClassName());
                if (DollarBidder.class.isAssignableFrom(clazz))
                    Thread.sleep(1000);
            } catch (ClassNotFoundException | InterruptedException e) {
                throw new RuntimeException(":(");
            }
        }
    }
}

Same logic as MBot, just use timeout instead of Exception when fighting against enemy. So far no one is defending agains timeout so should be effective

\$\endgroup\$
6
  • \$\begingroup\$ The stated rules forbid deliberately causing another bot to timeout. \$\endgroup\$ Apr 20, 2018 at 5:13
  • \$\begingroup\$ @WinstonEwert can you quote? I can't find rule disallowing this \$\endgroup\$
    – mleko
    Apr 20, 2018 at 6:12
  • \$\begingroup\$ "Sabotaging other bots is allowed, but attempting to alter field/method visibility will result in mysterious SecurityExceptions. An exception is causing another bot to break the 500ms limit." Also, I'm defending against timeout. \$\endgroup\$
    – RamenChef
    Apr 20, 2018 at 11:55
  • \$\begingroup\$ @RamenChef but this don't alter visibility of other elements. I'm not sure if I understand you correctly. Is provoking timeout allowed? \$\endgroup\$
    – mleko
    Apr 20, 2018 at 12:21
  • \$\begingroup\$ "An exception is causing another bot to break the 500ms limit." Specifically, this is an exception to the rule about sabotage. \$\endgroup\$
    – RamenChef
    Apr 20, 2018 at 14:14
2
\$\begingroup\$

One Extra

import net.ramenchef.dollarauction.DollarBidder;

public class OneExtra extends DollarBidder {
    @Override
    public int nextBid(int opponentsBid) {
        if(opponentsBid < 110)
          return opponentsBid + 6;
        return opponentsBid;
    }
}

Bids 6 more than the last bid, just because he can.

\$\endgroup\$
5
  • \$\begingroup\$ He can't bid 6 since all bids must be multiples of 5... \$\endgroup\$
    – Neil
    Apr 24, 2018 at 16:33
  • \$\begingroup\$ @Neil it's probably a typo... \$\endgroup\$
    – Stan Strum
    Apr 24, 2018 at 16:43
  • \$\begingroup\$ @Neil the rules specifically state: "Your bid does not need to be a multiple of 5¢" \$\endgroup\$
    – MegaTom
    Apr 25, 2018 at 2:14
  • \$\begingroup\$ @MegaTom Huh, well that was added since I last read the rules... \$\endgroup\$
    – Neil
    Apr 25, 2018 at 7:59
  • \$\begingroup\$ @Neil It was part of the original rules, but I added it there because it wasn't very obvious. \$\endgroup\$
    – RamenChef
    Apr 25, 2018 at 11:43
2
\$\begingroup\$

StackTraceObfuscaterBot

import net.ramenchef.dollarauction.DollarBidder;

import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;

public class StackTraceObfuscaterBot extends DollarBidder {
    private volatile static boolean created = false;
    private volatile DollarBidder pet;
    private boolean firstBid = false;

    public StackTraceObfuscaterBot() {
        if (created)
            throw new IllegalStateException("THERE CAN ONLY BE ONE!");
        created = true;
    }

    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        firstBid = true;
        RunnableFuture<DollarBidder> task = new FutureTask<>(() -> {
            try {
                return opponent.newInstance();
            } catch (Throwable t) {
                return null;
            }
        });
        Thread thread = new Thread(task);
        thread.start();
        try {
            pet = task.get(450, TimeUnit.MILLISECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            task.cancel(true);
            pet = null;
        }
    }

    @Override
    public int nextBid(int opponentsBid) {
        if (!firstBid)
            return 0;
        firstBid = false;

        for (int bid = opponentsBid + 5; i < 100; i += 5) {
            final int bidt = bid;
            RunnableFuture<Boolean> task = new FutureTask<>(() -> {
                pet.newAuction(this.getClass());
                return pet.nextBid(bidt) < bidt + 5;
            });
            Thread thread = new Thread(task);
            thread.start();
            try {
                if (task.get(23, TimeUnit.MILLISECONDS))
                    return bid;
            } catch (InterruptedException | ExecutionException | TimeoutException e) {
                task.cancel(true);
                return 0;
            }
        }
        return 0;
    }
}

This bot laughs at attempts to detect reflection via the stack trace. The closest thing they see to a DollarBidder is some lambda class it created. Clearly not another bot trying to reflect them. Little do they know that that lambda class is actually working for a DollarBidder. Beyond that, he acts like MarginalerBot.

\$\endgroup\$
1
  • \$\begingroup\$ Note that I've since updated my stack trace check to handle this. \$\endgroup\$
    – Nissa
    Apr 20, 2018 at 3:06
1
\$\begingroup\$

ImprovedAnalystBot (non-competing)

A lot of people seem to be using the AnalystBot code as a template, even though it's deliberately bad code. So I'm making a better template.

import net.ramenchef.dollarauction.DollarBidder;

public class ImprovedAnalystBot extends DollarBidder {
    private DollarBidder enemy;

    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        if (!opponent.equals(this.getClass()))
            try {
                this.enemy = opponent.newInstance();
                enemy.newAuction(this.getClass());
            } catch (Throwable t) {
                this.enemy = null;
            }
        else
            this.enemy = null;
    }

    @Override
    public int nextBid(int opponentsBid) {
        try {
            return enemy != null && enemy.nextBid(95) < 100 ? 95 : 0;
        } catch (Throwable t) {
            return 0;
        }
    }
}
\$\endgroup\$
6
  • \$\begingroup\$ Why not just edit your challenge? \$\endgroup\$ Apr 18, 2018 at 16:59
  • \$\begingroup\$ @NathanMerrill How would I edit it? \$\endgroup\$
    – RamenChef
    Apr 18, 2018 at 17:03
  • \$\begingroup\$ By clicking the edit button, and replacing AnalystBot with this code? \$\endgroup\$ Apr 18, 2018 at 17:06
  • \$\begingroup\$ @NathanMerrill AnalystBot is deliberately bad code so that it can demonstrate the AnalystKiller sabotaging it. \$\endgroup\$
    – RamenChef
    Apr 18, 2018 at 17:09
  • 1
    \$\begingroup\$ The AnalystKiller still works with the improved one :) The issue with making it a post is that the challenge is far more visible than an answer. \$\endgroup\$ Apr 18, 2018 at 17:10
1
\$\begingroup\$

MBot

import net.ramenchef.dollarauction.DollarBidder;

import java.util.Arrays;

public class MBot extends DollarBidder {
    protected DollarBidder rival = null;
    protected boolean rivalPrepared = false;
    protected Class<? extends DollarBidder> rivalClass;


    @Override
    public void newAuction(Class<? extends DollarBidder> opponent) {
        this.rivalClass = opponent;
        this.rivalPrepared = false;
    }

    protected DollarBidder getRival() {
        if (!rivalPrepared) {
            rivalPrepared = true;
            try {
                rival = rivalClass.newInstance();
                rival.newAuction(this.getClass());
            } catch (Throwable t) {
                rival = null;
            }
        }
        return rival;
    }

    @Override
    public int nextBid(int opponentsBid) {
        return calcBid(opponentsBid, isPeeking(3), isPeeking(4));
    }

    protected int calcBid(int opponentsBid, boolean isPeeking, boolean isSubPeeking) {
        if (isPeeking) {
            throw new RuntimeException();
        }

        for (int iBid = opponentsBid + 5; iBid <= 100; iBid = iBid + 5) {
            try {
                if (getRival().nextBid(iBid) < iBid + 5) {
                    return iBid;
                }
            } catch (Throwable t) {
                // noop
            }
        }
        return 0;
    }

    protected boolean isPeeking(int level) {
        final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        final StackTraceElement[] stackTraceElements = Arrays.copyOfRange(stackTrace, level, stackTrace.length);
        for (StackTraceElement ste : stackTraceElements) {
            try {
                Class<?> clazz = Class.forName(ste.getClassName());
                if (DollarBidder.class.isAssignableFrom(clazz))
                    return true;
            } catch (ClassNotFoundException e) {
                return true;
            }
        }
        return false;
    }
}

Slightly refined MarginalerBot

  • be unkind to those who wan't to check you
  • allow paying 100 to get 100 and break even case, just to deny others easy money
\$\endgroup\$
7
  • \$\begingroup\$ You can't declare nextBid to throw ClassCastException. \$\endgroup\$
    – RamenChef
    Apr 19, 2018 at 21:17
  • \$\begingroup\$ @RamenChef ok, swapped it to RuntimeException which don't require declaration :) \$\endgroup\$
    – mleko
    Apr 20, 2018 at 4:17
  • \$\begingroup\$ Your code for stack trace checking looks suspiciously similar to mine. \$\endgroup\$
    – Nissa
    Apr 23, 2018 at 18:19
  • \$\begingroup\$ @StephenLeppik probably it's copy of it \$\endgroup\$
    – mleko
    Apr 23, 2018 at 18:52
  • \$\begingroup\$ @mleko why though? The class it's copied from is an abstract superclass that's free to use. \$\endgroup\$
    – Nissa
    Apr 23, 2018 at 19:05
0
\$\begingroup\$

TimeRandBot

import net.ramenchef.dollarauction.DollarBidder;

public class TimeRandBot extends DollarBidder {

    @Override
    public int nextBid(int opponentsBid) {
        return (int) (System.currentTimeMillis() % 21 * 5);
    }
}

Bids from 0¢ up to 100¢ inclusive in increments of 5¢.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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