Dollar Bill Auction

32

10

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!

RamenChef

Posted 2018-04-17T12:59:07.253

Reputation: 1 163

11This 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) – Nathan Merrill – 2018-04-17T21:28:27.600

2"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.. – Kevin Cruijssen – 2018-04-18T14:59:55.183

1

Comments are not for extended discussion; this conversation has been moved to chat.

– James – 2018-04-18T15:49:12.677

You really should specify that bots need to be in the default package. – Nathan Merrill – 2018-04-18T17:12:56.073

@NathanMerrill Put your bot in any package you like, but if you put it in the runner package, it's breaking rule 3. – RamenChef – 2018-04-18T17:15:19.627

@RamenChef no it's not. If I put it in the runner package, and only access the DollarBidder class, that's still valid behavior. – Nathan Merrill – 2018-04-18T17:24:32.750

1@histocrat "The loser pays their previous bid". Yes; the bot would pay its last legal bid. – RamenChef – 2018-04-18T19:16:43.763

What happens if a bot exceeds the 500ms restriction? Does it lose like it would if it threw an error? – None – 2018-04-18T20:18:38.743

4Also: what if my bot causes other bots to exceed the 500ms restriction? – Nathan Merrill – 2018-04-18T20:30:34.137

If your bot regularly goes beyond the 500ms restriction, it’s an invalid submission. That being said, I don’t actually expect anybody to come close to 500ms. – RamenChef – 2018-04-18T20:33:45.980

Preliminary results – RamenChef – 2018-04-18T22:02:36.013

4@RamenChef We're talking about malicious code here. What if I detect when another bot is calling me, and call Thread.sleep(1000)? – Nathan Merrill – 2018-04-19T14:04:35.153

Then the counter bot would be invalid. Also, there is a counter to that. – RamenChef – 2018-04-19T15:19:16.880

Sorry, who is the "counter bot"? The one sleeping? – Nathan Merrill – 2018-04-19T18:54:35.407

@NathanMerrill yes. – RamenChef – 2018-04-19T19:39:31.843

3I'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?) – Nathan Merrill – 2018-04-19T20:04:08.690

"Attacking the runner" is vague, but it should be fairly obvious whether a bot is breaking it or not. As for the malicious code, it's no longer allowed as a form of sabotage. My defense of the question is this: are you really going to close the question over a nitpick? – RamenChef – 2018-04-19T21:24:25.307

2@RamenChef Yes, I do think it should be closed: These are solvable issues, but they aren't small; We need challenges to be objective on this site. Decisions about whether something is valid shouldn't just be whatever the OP decides, it needs to be objectively defined in the spec. – Nathan Merrill – 2018-04-20T01:42:09.793

@NathanMerrill I feel like the issues raised - although avoidable, important and abundant - don't invalidate the challenge in its entirety. People are having fun anyway, right? Heck, even Formic Functions, which won Best of PPCG 2017, relies on sportsmanlike behavior of participants to not devolve into everlasting edit wars.

– Alion – 2018-04-20T13:49:26.713

Just to be clear, I would've probably voted to close it if I paid closer attention at the start, though. – Alion – 2018-04-20T13:54:40.423

1@Alion That edit war wasn't banned by the rules though. That was a "whoever edits last wins" (the one-up scenario). That challenge still had objective rules. – Nathan Merrill – 2018-04-20T14:27:18.207

1Could you have DollarBidder implement IntUnaryOperator? – Nissa – 2018-04-23T19:20:29.873

@StephenLeppik ...why? If you need to use the nextBid method as a callback, you can use DollarBidder::nextBid. – RamenChef – 2018-04-23T19:32:00.120

Correction: use instance::nextBid. – RamenChef – 2018-04-24T13:35:00.793

... I'm still unclear as to where the 5¢ comes in to play... – Neil – 2018-04-25T08:04:07.743

Answers

2

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

mleko

Posted 2018-04-17T12:59:07.253

Reputation: 290

1This isn't fair. – Joshua – 2018-11-12T22:57:11.313

@Joshua What particularly isn't fair about this solution in your opinion? – mleko – 2018-11-13T05:56:12.797

Knowing the names of your opponents. – Joshua – 2018-11-13T14:37:05.853

@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 – mleko – 2018-11-13T18:31:41.047

Now the question gets a downvote too. – Joshua – 2018-11-13T18:57:26.243

@Joshua will you downvote all answers which are One-Upping - that would be fair I guess :) – mleko – 2018-11-13T19:59:24.193

1Already did.... – Joshua – 2018-11-13T21:01:29.150

15

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¢.

Nissa

Posted 2018-04-17T12:59:07.253

Reputation: 3 334

11

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

};

Silvio Mayolo

Posted 2018-04-17T12:59:07.253

Reputation: 1 817

1Nah, insider trading would be if the RichJerk bot made a specific exception for your bot and bid $0 for it. – Nissa – 2018-04-17T23:02:22.333

It's too early to be optimizing against other answers. Also, it's AnalystBot, not Analyst. – RamenChef – 2018-04-17T23:14:46.303

8Probably there needs to be a rule "class names will be randomized". – user202729 – 2018-04-18T00:02:00.637

1@user202729 How about "no direct references to classes"? – RamenChef – 2018-04-18T12:45:28.477

@RamenChef Also fine, but then you would have to read classes' source code (and handle the md5(sha256(classname))=='1234' cases), but this can be automated. – user202729 – 2018-04-18T12:52:27.677

1I'd like to see this handle the MimicBot. – Nissa – 2018-04-18T12:59:23.657

8

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

dkudriavtsev

Posted 2018-04-17T12:59:07.253

Reputation: 5 781

6You nuked Analyst spectacularly. – Silvio Mayolo – 2018-04-17T22:18:56.390

@SilvioMayolo How? – dkudriavtsev – 2018-04-17T22:20:11.343

Mirror tries to emulate Analyst playing against itself, resulting in a stack overflow. – Silvio Mayolo – 2018-04-18T02:14:52.710

8

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.

Winter

Posted 2018-04-17T12:59:07.253

Reputation: 209

score is package-protected. Your bots can't access it. – RamenChef – 2018-04-19T17:04:11.173

@RamenChef Oops, removed the CheatingBot – Winter – 2018-04-19T17:10:47.540

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:) – Nathan Merrill – 2018-04-19T18:52:21.920

7

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.

user48543

Posted 2018-04-17T12:59:07.253

Reputation:

7

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.

Qaghan

Posted 2018-04-17T12:59:07.253

Reputation: 71

Welcome to PPCG! – Martin Ender – 2018-04-18T13:23:56.773

1It’s simple, but it beats all of the other bots by a fairly large margin! – RamenChef – 2018-04-18T22:09:01.310

6

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.

AdmBorkBork

Posted 2018-04-17T12:59:07.253

Reputation: 41 581

6

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.

histocrat

Posted 2018-04-17T12:59:07.253

Reputation: 20 600

1

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.)

– Arnauld – 2018-04-21T00:45:36.980

6

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.

Neil

Posted 2018-04-17T12:59:07.253

Reputation: 95 035

"Bids go up in increments of 5¢". Your bot currently isn't doing so. – Kevin Cruijssen – 2018-04-18T12:12:43.577

1@KevinCruijssen Fair enough. I also changed the upper limit so it could bid the whole $1, just in case, – Neil – 2018-04-18T12:14:27.710

5

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

};

Silvio Mayolo

Posted 2018-04-17T12:59:07.253

Reputation: 1 817

2How 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. – Freiheit – 2018-04-17T21:43:50.177

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. – Silvio Mayolo – 2018-04-17T21:56:14.163

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. – Kevin Cruijssen – 2018-04-18T12:11:51.593

5

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.

Nissa

Posted 2018-04-17T12:59:07.253

Reputation: 3 334

4

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.

Kevin Cruijssen

Posted 2018-04-17T12:59:07.253

Reputation: 67 575

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 – Stan Strum – 2018-04-17T16:41:34.650

@RamenChef Typo.. But I've modified the entire bot. It had some bugs anyway.. – Kevin Cruijssen – 2018-04-17T18:07:03.540

4This can absolutely lose money. If you bid 100, then your opponent bids 105, you end up losing 100, and they only lose 5. – None – 2018-04-17T18:53:11.300

@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. – Kevin Cruijssen – 2018-04-17T18:56:39.293

1I think you mean "lose" not "loose". Lose is the opposite of win. Loose is the opposite of tight. – Kat – 2018-04-19T22:09:52.527

@Kat Thanks. English is not my native language, although I'm not too bad in it if I saw so myself. "Loose" was something I always did like this as opposite of "win", so thanks for the correction. Will try to remember it from now on. :) – Kevin Cruijssen – 2018-04-20T06:29:32.940

@KevinCruijssen it's an extremely common spelling mistake among native speakers, so don't feel too bad. =] Your English is indeed quite good. – Kat – 2018-04-20T20:11:15.380

4

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.

Nissa

Posted 2018-04-17T12:59:07.253

Reputation: 3 334

4

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.

user48543

Posted 2018-04-17T12:59:07.253

Reputation:

This would be excellent against MirrorBot, MarginalerBot, and probably also MimicBot. – Nissa – 2018-04-19T12:57:58.870

@StephenLeppik That's what I was thinking when I made it. Still plenty of weaknesses, though. – None – 2018-04-19T13:08:53.537

+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)? – Kevin Cruijssen – 2018-04-19T13:35:12.483

@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. – None – 2018-04-19T13:40:19.460

3

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.

dkudriavtsev

Posted 2018-04-17T12:59:07.253

Reputation: 5 781

Watch out for the AnalystKiller. – RamenChef – 2018-04-17T22:17:05.103

@RamenChef AFAIK the analyst killer just throws an exception if it sees itself being analyzed. I can catch that – dkudriavtsev – 2018-04-17T22:18:52.127

1You probably should catch it. – RamenChef – 2018-04-17T22:24:14.750

@RamenChef No idea if that will work, I can't Java – dkudriavtsev – 2018-04-18T00:20:40.733

3

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.

Kevin Cruijssen

Posted 2018-04-17T12:59:07.253

Reputation: 67 575

1MirrorBot 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. – Nissa – 2018-04-18T13:53:53.277

@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. – Kevin Cruijssen – 2018-04-18T15:30:00.477

What you have right now is a pretty good counter to MirrorBot: throw if you encounter yourself. – Nissa – 2018-04-18T16:19:52.170

3Holy ternary chaining, Batman! – Skyler – 2018-04-18T16:36:28.003

1Also, when playing BorkBorkBot, shouldn't you raise to 95 when they hit 85? Otherwise you're both bidding 95 if they start. – Skyler – 2018-04-18T16:42:08.903

@Skyler Thanks for noticing, fixed! As for the ternary, it's shorter and still pretty readable than all those else-ifs I had before. ;) – Kevin Cruijssen – 2018-04-18T17:12:32.630

Have you considered using my AbstractAnalystCounterBot? – Nissa – 2018-04-18T17:37:29.873

Code review - RichJerk and GreedyBot are covered by the default case. I also think theres some way to rejigger a few of these cases to just bid 5 more. – Freiheit – 2018-04-18T19:43:55.603

@Qaghan Thanks, should be fixed now. – Kevin Cruijssen – 2018-04-18T21:34:49.007

1@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. – Kevin Cruijssen – 2018-04-18T21:36:38.847

For some of these counters it seems like you're assuming you'll get the opponent's losses added to your winnings, which isn't the case. For example, against DeterrentBot, you won't win 95 cents by bidding 105 over its 100, you'll lose 5 cents (and DeterrentBot will lose 100 cents). I guess if you're thinking in zero sum terms that's like winning 95 cents, but it won't help with your overall score. – histocrat – 2018-04-19T19:47:06.220

@histocrat Ah, that was just a mistake in the explanation of the DeterrentBot. You're completely right. They loose 100, I loose 5. Fixed the explanation. If you see any other mistakes let me know. I know I won't get the amount my opponents bid, so if any of the other incorrectly reflects that it would be a mistake on my part as well.. – Kevin Cruijssen – 2018-04-19T19:54:21.427

3

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.

user48543

Posted 2018-04-17T12:59:07.253

Reputation:

3

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.

ripkoops

Posted 2018-04-17T12:59:07.253

Reputation: 61

1Welcome to PPCG! – Alion – 2018-04-19T10:21:07.397

3

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.

Cain

Posted 2018-04-17T12:59:07.253

Reputation: 1 149

3

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.

Winston Ewert

Posted 2018-04-17T12:59:07.253

Reputation: 1 051

2

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

Kevin Cruijssen

Posted 2018-04-17T12:59:07.253

Reputation: 67 575

2

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.

Winston Ewert

Posted 2018-04-17T12:59:07.253

Reputation: 1 051

Mind if I port isPeeking into AbstractAnalystCounterBot? – Nissa – 2018-04-20T02:03:22.087

1@StephenLeppik, well, I stole it from MBot... – Winston Ewert – 2018-04-20T02:31:40.810

1Well, MBot probably stole it from me… – Nissa – 2018-04-20T12:50:00.127

2

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.

RamenChef

Posted 2018-04-17T12:59:07.253

Reputation: 1 163

Note that I've since updated my stack trace check to handle this. – Nissa – 2018-04-20T03:06:46.613

2

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.

MegaTom

Posted 2018-04-17T12:59:07.253

Reputation: 3 787

He can't bid 6 since all bids must be multiples of 5... – Neil – 2018-04-24T16:33:44.870

@Neil it's probably a typo... – Stan Strum – 2018-04-24T16:43:10.873

@Neil the rules specifically state: "Your bid does not need to be a multiple of 5¢" – MegaTom – 2018-04-25T02:14:26.850

@MegaTom Huh, well that was added since I last read the rules... – Neil – 2018-04-25T07:59:58.133

@Neil It was part of the original rules, but I added it there because it wasn't very obvious. – RamenChef – 2018-04-25T11:43:54.970

1

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.

Insanity

Posted 2018-04-17T12:59:07.253

Reputation: 41

2The security manager would stop this. – Nissa – 2018-04-18T01:53:36.973

2And this wouldn't work anyway since nowhere in the runner does it box its integers. – Nissa – 2018-04-18T02:00:23.650

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." – NoOneIsHere – 2018-04-18T02:27:18.740

1@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. – NoOneIsHere – 2018-04-18T02:32:15.573

@NoOneIsHere That also doesn't box any integers and so wouldn't be affected. – Nissa – 2018-04-18T13:30:36.903

@StephenLeppik oh, I see. I don't use Java. – NoOneIsHere – 2018-04-18T14:57:30.430

1Fails to compile because of unchecked exceptions. – Nissa – 2018-04-18T20:50:55.640

1

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

RamenChef

Posted 2018-04-17T12:59:07.253

Reputation: 1 163

Why not just edit your challenge? – Nathan Merrill – 2018-04-18T16:59:47.407

@NathanMerrill How would I edit it? – RamenChef – 2018-04-18T17:03:19.037

By clicking the edit button, and replacing AnalystBot with this code? – Nathan Merrill – 2018-04-18T17:06:58.253

@NathanMerrill AnalystBot is deliberately bad code so that it can demonstrate the AnalystKiller sabotaging it. – RamenChef – 2018-04-18T17:09:23.313

1The 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. – Nathan Merrill – 2018-04-18T17:10:21.717

This would catch the exception thrown by AnalystKiller. – RamenChef – 2018-04-18T17:12:33.590

1

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

mleko

Posted 2018-04-17T12:59:07.253

Reputation: 290

You can't declare nextBid to throw ClassCastException. – RamenChef – 2018-04-19T21:17:17.630

@RamenChef ok, swapped it to RuntimeException which don't require declaration :) – mleko – 2018-04-20T04:17:27.260

Your code for stack trace checking looks suspiciously similar to mine. – Nissa – 2018-04-23T18:19:36.123

@StephenLeppik probably it's copy of it – mleko – 2018-04-23T18:52:15.097

@mleko why though? The class it's copied from is an abstract superclass that's free to use. – Nissa – 2018-04-23T19:05:29.640

@StephenLeppik well the original ripped code was a little bit different than what I needed, later we converged again – mleko – 2018-04-23T20:02:53.590

@mleko at the very least, you're missing an update to counter the StackTraceObfuscatorBot. – Nissa – 2018-04-23T22:23:02.553

1

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

mleko

Posted 2018-04-17T12:59:07.253

Reputation: 290

The stated rules forbid deliberately causing another bot to timeout. – Winston Ewert – 2018-04-20T05:13:46.277

@WinstonEwert can you quote? I can't find rule disallowing this – mleko – 2018-04-20T06:12:51.860

"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. – RamenChef – 2018-04-20T11:55:06.030

@RamenChef but this don't alter visibility of other elements. I'm not sure if I understand you correctly. Is provoking timeout allowed? – mleko – 2018-04-20T12:21:46.557

"An exception is causing another bot to break the 500ms limit." Specifically, this is an exception to the rule about sabotage. – RamenChef – 2018-04-20T14:14:13.523

@RamenChef Ok I checked rules again, and yes rule about that was added recently :) – mleko – 2018-04-20T14:38:06.197