28
\$\begingroup\$

Background

The game of Morra is a simple game. In the "original" version, several players simultaneously throw out a number 0-5 with their hands while guessing the total sum of everyone's hands. The version I'll use here has been modified to increase the potential for non-trivial strategy, and it is described below:

  • There are two players.
  • Like in rock-paper-scissors, the players move simultaneously.
  • Each turn, each player chooses a number 0-5 and also guesses their opponents choice of 0-5. This means that two numbers are output each turn. To clarify, both numbers output should be in the range 0-5, inclusive.
  • If you guess your opponent's choice correctly but your opponent did not guess correctly, you win a certain number of points equal to the sum of the two numbers played. For example, if the numbers played were 3 and 5, a correct guess would be worth 8 points.
  • If both or neither players guess correctly, no points are awarded.
  • The person with the most points after 1000 rounds wins that game.

The Tournament

The tournament will be done in a round-robin style and will be run by creating each possible pairing of contestant. For each victory, the contestant gains 2 victory points. Each tie results in 1 victory point. No victory points are gained for a loss.

Intuitively, the winner of the tournament shall be the contestant with the most victory points against others.


How to Enter

There will be two methods of submitting bots to compete. The first, and much preferred method, is to implement a Java interface supplied by the controller. The second method is to write an independent program.

Let's cover the Java method first. The interface you will need to implement is Player and it defines two methods: public String getName() identifies your bot, and public int[] getMove(String[] args) takes args as an array of six strings, mychoices myguesses myscore opponentchoices opponentguesses opponentscore. An example is the following:

042 045 0 324 432 6

This means that I chose 0 on the first round and guessed that my opponent was going to throw a 0. My opponent threw a 3 and guessed I would throw a 4. In the third round, my opponent made the correct guess that I would throw a 2, meaning that he gains 2+4=6 points.

Your method will return an array of two integers, which are your choice and guess, respectively. An example is {4,2}for a choice of 4 and a guess of 2.

Here is an example of a complete Java bot written as a method. If you want, your submission only has to include what's going in the getMove method.

import java.util.Random;
/**
 * A simple example Morra bot to get you started.
 */
public class ExampleBot implements Player
{
    public String getName()
    {
        return "ExampleBot";
    }

    public int[] getMove(String [] args)
    {
        //easiest way I know to break down to create a move history
        //(just contains their throw history)
        char[] theirThrowsC = args[3].toCharArray();
        int[] theirThrows = new int[theirThrowsC.length];
        for(int i = 0; i < theirThrowsC.length; i++)
        {
            theirThrows[i] = Integer.parseInt(Character.toString(theirThrowsC[i]));
        }

        //get my score
        int myScore = Integer.parseInt(args[2]);

        Random r = new Random();
        int guess = r.nextInt(6);
        if(theirThrows.length > 0)
        {
            guess = theirThrows[theirThrows.length-1];
        }

        //throws a random number, guesses what they threw last
        return new int[] {r.nextInt(6),guess}; 
    }

    public static int otherMethod(int example) //you can write additional static methods
    {
        return 0;
    }
}

As an Independent Program

I am currently limited in my support of additional languages. Besides Java, I can accept programs written in Python 3.4, Perl 5, or Ruby 2.1.5. If there is a language that several people seem to want, I'll do my best to add it.

The input to your program will be arguments on the command line. It could look like this:

perl awesomebot.plx 042 045 0 324 432 6

The output of your program should be your choice followed by your guess, each followed by whitespace.

Please include in your answer the exact command needed to run it. Keep in mind that I'm running Windows 8.1.


Extra Rules

Saving State and Timeouts

Your program will be allowed to create one text file in the local directory, where you can store information. This information will kept throughout the tournament but deleted afterwards. Give the file a name I can identify.

There is a time limit of 500 milliseconds for your code to respond. Failure to respond in the time limit (or giving an invalid move) will result in forfeiture of that particular match. Java submissions currently have a passive timeout (which I may upgrade to active), whereas non-Java submissions have an active timeout where their process is terminated after 500 milliseconds.

More submission rules

  • You are allowed multiple submissions, as long as they abide by the rules and don't tag-team.
  • Each entry must be unique. You can't make an exact copy of another bot's logic in a different language.
  • The bots cannot interact with each other (to form a team of any sort).
  • You can't use the logic of the other bots inside of your bot to, say, identify your competitor and predict its actions. You can, of course, try to determine the strategy of your opponent.
  • Don't attempt to mess with the controller, other contestants, or my computer. Don't connect to external information sources.

The Controller

The current version of the controller is found here. It is written in Java 8. The "Tournament" file is the main controller, which also contains the list of competitors (if you want to host your own competitions).


Leaderboard

I haven't really been able to update the leaderboard very often. I am rather busy this weekend. By "rather busy" I mean no access to a computer from 6:30 AM to 9:30 PM. Here are the scores after 5 runs. The "Echo" bot kept forfeiting for some reason (might be my fault, I haven't investigated yet).

  170 - Quinn and Valor                         
  158 - Historian                               
  142 - DeltaMax                                
  140 - MorraCowbell                            
  132 - Extrapolator                            
  115 - Rainbolt                                
  102 - Popularity                              
  100 - Interpolator                            
   83 - CounterBot                              
   80 - Basilisk                                
   76 - Erratica                                
   65 - Trendy                                  
   63 - Scholar                                 
   62 - RandomGuesser                           
   60 - KingFisher                              
   59 - NullifierBot                            
   55 - EvolvedBot                              
   48 - Confused          

Credit

Many thanks to Rainbolt and Peter Taylor for their help with the controller.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ @MartinBüttner Ruby 2.1.5 has been added. \$\endgroup\$
    – PhiNotPi
    Feb 12, 2015 at 2:47
  • \$\begingroup\$ How does the round robin work? Player1 vs Player2 1000 times, Player1 vs Player3 1000 times etc... OR is it Player1 vs Player2 once then player1 vs player 3 once etc... \$\endgroup\$
    – Vajura
    Feb 12, 2015 at 13:22
  • \$\begingroup\$ @Vajura A single tournament is composed of 1 battle between each pair. One battle has 1000 rounds, with the highest total score at the end of the battle determining who gets the two victory points. The current scoreboard shows the total victory points after 40 tournaments. \$\endgroup\$
    – PhiNotPi
    Feb 12, 2015 at 13:38
  • \$\begingroup\$ Sorry for the delays in updating the board. I'm extremely busy this weekend. Expect and update tonight and tomorrow morning. \$\endgroup\$
    – PhiNotPi
    Feb 13, 2015 at 11:50
  • \$\begingroup\$ Wow, I didn't expect my bot to do so well! Also, what do the numbers mean by the first set of results... number of wins? \$\endgroup\$
    – mbomb007
    Feb 13, 2015 at 21:22

21 Answers 21

18
+100
\$\begingroup\$

Morra Cowbell

For anyone looking for significance in the name of this bot, the name Morra makes me think of Space Italian, so I reckoned I needed a name which played on that. Other candidates included Morra fool you and Morra for me.

This is a full class implementing the Player interface. Explanation below.

import java.util.Random;

public class MorraCowbell implements Player {
    private final Random rnd = new Random();

    public String getName() {
        return "MorraCowbell";
    }

    public int[] getMove(String[] args) {
        int[] prior = new int[36];
        for (int i = 0; i < 36; i++) prior[i] = 1;
        // args: myChoices myGuesses myScore opponentChoices opponentGuesses opponentScore
        if (args.length == 6 && args[3].length() == args[4].length()) {
            for (int i = 0; i < args[3].length(); i++) prior[6*(args[3].charAt(i) - '0') + (args[4].charAt(i) - '0')]++;
        }

        int[] weights = new int[6];
        for (int r = 0; r < 6; r++) {
            for (int s = 0; s < 6; s++) {
                for (int t = 0; t < 6; t++) {
                    weights[r] += (r + s) * ((r + s == 5 ? 1 : 0) + (r == t ? -1 : 0)) * prior[s * 6 + t];
                }
            }
        }

        // Find the best window.
        int[][] magic = new int[][] {
            { 7776, 6480, 5400, 4500, 3750, 3125 }, { 3125, 2500, 2000, 1600, 1280, 1024 }, { 1875, 1500, 1200, 960,
            768, 640 }, { 1125, 900, 720, 576, 480, 400 }, { 1620, 1296, 1080, 900, 750, 625 }, { 1296, 1080, 900, 750,
            625, 500 }, { 750, 625, 500, 400, 320, 256 }, { 675, 540, 432, 360, 300, 250 }, { 648, 540, 450, 375, 300,
            250 }, { 375, 300, 250, 200, 160, 128 }, { 375, 300, 240, 200, 160, 128 }, { 450, 375, 300, 240, 192, 160,
            128 }, { 324, 270, 225, 180, 150, 125 }, { 270, 225, 180, 144, 120, 100, 80 }, { 225, 180, 150, 120, 96,
            80 }, { 225, 180, 144, 120, 96, 80 }, { 324, 270, 216, 180, 150, 125, 100, 80, 64 }, { 135, 108, 90, 72, 60,
            50 }, { 135, 108, 90, 75, 60, 50, 40, 32 }, { 108, 90, 75, 60, 48, 40, 32 }, { 54, 45, 36, 30, 25, 20, 16 },
            { 54, 45, 36, 30, 24, 20, 16 }
        };
        long bestN = 0;
        int bestD = 1, bestIdx = -1, bestA[] = null;
        for (int[] A : magic) {
            for (int i = 0; i < A.length - 5; i++) {
                long n = 0;
                int d = 0;
                for (int j = 0; j < 6; j++) {
                    n += weights[j] * A[i + j];
                    d += A[i + j];
                }
                if (n * bestD > bestN * d) {
                    bestN = n;
                    bestD = d;
                    bestIdx = i;
                    bestA = A;
                }
            }
        }

        int r = rnd.nextInt(bestD);
        for (int i = 0; i < 6; i++) {
            r -= bestA[bestIdx + i];
            if (r < 0) return new int[] { i, 5 - i };
        }

        // Just a precaution: this should be unreachable.
        return new int[] { 0, 5 };
    }
}

Explanation

I started by analysing games with fewer fingers. The simplest non-trivial one allows calls of 0 or 1 and has the following payoff table (values are payoff for the row player):

       (0,0) (0,1) (1,0) (1,1)
      +-----------------------
(0,0) |  0     0    -1     0
(0,1) |  0     0     0     1
(1,0) |  1     0     0    -1
(1,1) |  0    -1     1     0

The (0,0) strategy is dominated by (0,1), so we can reduce the table to

       (0,1) (1,0) (1,1)
      +-----------------
(0,1) |  0     0     1
(1,0) |  0     0    -1
(1,1) | -1     1     0

Now the (1,0) strategy is dominated by (0,1), so we can further reduce the table to

       (0,1) (1,1)
      +-----------
(0,1) |  0     1
(1,1) | -1     0

And now (1,1) is dominated by (0,1), so we end up with

       (0,1)
      +-----
(0,1) |  0  

Therefore always playing (0,1) is a Nash equilibrium. But the curious thing is that it's not the only one. This is a symmetric zero-sum game, so the expected payoff is 0, and any mixed strategy combining (0,1) and (1,0) where (0,1) is picked at least 50% of the time achieves that payoff. So we have a one-dimensional space of Nash equilibria.

It seems to be the case, although I haven't proven it, that n-finger Morra has an n-dimensional polytope of Nash equilibria which are mixed strategies between the n+1 (pick, guess) pairs for which pick + guess = n.

The magic numbers in the code above encode the 32 vertices of the 5-dimensional polytope of Nash equilibria. I found them by setting up a linear programming instance which represented the polytope and then using random objective functions. The reason for encoding all 32 rather than picking one is simple: the expected payoff is 0, so I need to do better than expected to get a win. I essentially assume that the other player is using a mixed strategy and estimate the distribution based on their pick history. Then I select the polytope vertex which maximises my expected gain against that estimated distribution.

QuinnAndValor demonstrates the vulnerability of the assumption that the other player is using a mixed strategy. By detecting a player which uses the strategies from the Nash equilibria it is able to switch into a random walk mode where, playing a non-equilibrium strategy, it is liable on average to lose, but it only needs to gain a lead once and then it can revert to playing pairs for which pick + guess = n. So the Nash equilibria for a single game don't generalise trivially to Nash equilibria for the repeated game, which allows more complex strategies.

\$\endgroup\$
3
  • 4
    \$\begingroup\$ Is it possible that your magic contains a part of the Hamming Numbers? It certainly doesn't contain all of them, but many (or all?) of them are in the list on that website. \$\endgroup\$
    – GiantTree
    Feb 11, 2015 at 18:13
  • \$\begingroup\$ @GiantTree, they are all Hamming numbers. Interesting observation. \$\endgroup\$ Feb 11, 2015 at 22:29
  • \$\begingroup\$ No wonder your bot is going ham. :D \$\endgroup\$
    – mbomb007
    Feb 13, 2015 at 21:23
11
\$\begingroup\$

Quinn and Valor (Updated)

Quinn and Valor are an elite ranger team. With crossbow and claw, they rip apart every opponent dares to challenge them.

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

interface Champion extends Player {
}

/*
 * Quinn and Valor are an elite ranger team. With crossbow and claw, they ...
 */
public class QuinnAndValor implements Champion {

    private final Champion quinn = new Quinn();
    private final Champion valor = new Valor();

    private int checker;
    private int myScore, opScore;
    private boolean ulted;
    private boolean teemoDetected;
    private boolean quinnNeverLose, valorNeverLose;
    private int quinnScore, valorScore;
    private int quinnRound, valorRound;

    public QuinnAndValor() {
        checker = check() ? 0 : 1;
    }

    // Check if is a fine use
    private static boolean check() {
        return Thread.currentThread().getStackTrace()[3].getClassName().equals(
                "Tournament");
    }

    @Override
    public String getName() {
        return quinn + " and " + valor;
    }

    @Override
    public int[] getMove(String[] args) {
        // Punish for bad usage
        switch (checker) {
        case 1:
            checker++;
            return new int[] { -1, -1 };
        case 2:
            checker++;
            return null;
        case 3:
            throw new Error("Mua he he heh!");
        default:
            if (checker > 0)
                throw new Error("Mua he he heh!");
            break;
        }

        int round = args[0].length();
        if (round == 0) {
            // Buy starting items
            myScore = opScore = 0;
            teemoDetected = false;
            quinnNeverLose = valorNeverLose = true;
            quinnScore = valorScore = quinnRound = valorRound = 0;
            ((Valor) valor).reset();
        }

        if (ulted = useUltimate(args)) {
            valorRound++;
            return valor.getMove(args);
        } else {
            quinnRound++;
            return quinn.getMove(args);
        }
    }

    /*
     * Quinn's ultimate has a lengthy cool-down, especially at lower ranks, so
     * we have to use it only when needed.
     */
    private boolean useUltimate(String[] args) {
        int round = args[0].length();
        int lastMyScore = myScore;
        int lastOpScore = opScore;
        myScore = Integer.parseInt(args[2]);
        opScore = Integer.parseInt(args[5]);
        int score = (myScore - opScore) - (lastMyScore - lastOpScore);
        if (ulted) {
            valorScore += score;
            valorNeverLose &= score >= 0;
        } else {
            quinnScore += score;
            quinnNeverLose &= score >= 0;
        }

        if (round < 100) {
            // Haven't hit level 6 yet
            return false;
        }

        if (myScore > opScore) {
            // We're already winning. Press on with strategy impossible to lose
            if (quinnNeverLose && quinnRound >= 50)
                return false;
            if (valorNeverLose && valorRound >= 50)
                return true;
        } else if (myScore < opScore) {
            // Although Quinn can blind others to counter them, she can be
            // counter be Teemo who also has blind! Don't fall for this!
            if (!teemoDetected) {
                teemoDetected = true;
                for (int i = round - 20; i < round; i++)
                    if (args[3].charAt(i) + args[4].charAt(i) != 'e')
                        teemoDetected = false;
            }
            if (teemoDetected)
                return true;
        }

        if (valorRound < 100) {
            // If we never use our ultimate, how can we know how strong it is?
            return true;
        }

        if (quinnScore < 0 && valorScore < 0)
            return valorRound < quinnRound;
        else
            return quinnScore * valorRound < valorScore * quinnRound;
    }

    @Override
    public String toString() {
        return getName();
    }

    /*
     * Quinn is a female Demacian elite ranger.
     * 
     * @see Valor
     */
    public static class Quinn implements Champion {
        @Override
        public String getName() {
            return "Quinn";
        }

        /*
         * Magic!
         */
        @Override
        public int[] getMove(String[] args) {
            int t = (int) ((Math.sqrt(Math.random() * 168 + 1) - 1) / 2);
            return new int[] { 5 - t, t };
        }

        @Override
        public String toString() {
            return getName();
        }
    }

    /*
     * Valor is Quinn's Demacian eagle.
     * 
     * @see Quinn
     */
    public static class Valor implements Champion {
        @Override
        public String getName() {
            return "Valor";
        }

        private int lastRound;
        private double[][] c;

        public void reset() {
            lastRound = 0;
            c = new double[6][6];
        }

        /*
         * Magic!
         */
        @Override
        public int[] getMove(String[] args) {
            int round = args[0].length();
            int[] b = new int[6];
            for (int i = round - 12; i < round; i++)
                b[args[0].charAt(i) - '0']++;
            {
                double deWeight = Math.pow(0.95, round - lastRound);
                for (int i = 0; i < 6; i++)
                    for (int j = 0; j < 6; j++)
                        c[i][j] *= deWeight;
                double weight = 1;
                for (int i = round - 1; i >= lastRound; i--) {
                    c[args[3].charAt(i) - '0'][args[4].charAt(i) - '0'] += weight;
                    weight *= 0.95;
                }
            }
            lastRound = round;

            List<int[]> pq = new ArrayList<>(1);
            double e = Integer.MIN_VALUE;
            for (int i = 0; i < 6; i++)
                for (int j = 0; j < 6; j++) {
                    double f = 0;
                    for (int k = 0; k < 6; k++)
                        f += (i + j) * c[j][k];
                    for (int k = 0; k < 6; k++)
                        f -= (i + k) * c[k][i];
                    // recently played moves are dangerous
                    f -= b[i] * b[i] * ((round + 11) / 12);
                    if (f >= e) {
                        if (f > e) {
                            pq.clear();
                            e = f;
                        }
                        pq.add(new int[] { i, j });
                    }
                }
            return pq.get((int) (Math.random() * pq.size()));
        }

        @Override
        public String toString() {
            return getName();
        }
    }
}

They almost always win against all Java solution on my machine.

Edit:

I admit Quinn and Valor failed to duel Historian, but I still have good faith in them to win the tournament.

My principle is, for any solution with choice + guess == 5, also playing with choice + guess == 5 grantees keeping your advantage.

Update:

Well... everything just got complicated.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I like the League of Legends reference. I really want to make a Teemo bot now. :) \$\endgroup\$
    – mbomb007
    Feb 16, 2015 at 21:13
6
\$\begingroup\$

Scholar

Scholar tries to learn from the moves of its opponent, choosing the one his opponene less guessed and guessing the one his opponent most used. But theory isn't everything, so Scholar doesn't do very well...

import java.util.HashMap;

public class Scholar implements Player
{
    public static int[] pm = new int[6];
    public static int[] pg = new int[6];
    public static HashMap<Integer, Integer> lm = new HashMap<>();
    public static HashMap<Integer, Integer> lg = new HashMap<>();

    public String getName()
    {
        return "Scholar";
    }

    public int[] getMove(String[] a)
    {
        int r = a[0].length();
        for (int i = 0; i < 6; i++) { pm[i]=0; pg[i]=0; }
        for (int i = 0; i < a[3].length(); i++) {
            int m = Integer.parseInt(String.valueOf(a[4].charAt(i)));
            int g = Integer.parseInt(String.valueOf(a[3].charAt(i)));
            pm[m]++; pg[g]++;
        }
        for (int i = 0; i < pm.length; i++) { lm.put(i, pm[i]); lg.put(i, pg[i]); }
    
        if (r < 1) {
            return new int[] { 3, 3 };
        } else {
        
            int mm = lm.entrySet().stream().min((x, y) -> x.getValue() > y.getValue() ? 1 : -1).get().getKey();
            int mg = lg.entrySet().stream().max((x, y) -> x.getValue() > y.getValue() ? 1 : -1).get().getKey();
            return new int[] { mm, mg };
        }   
    }
}
\$\endgroup\$
6
\$\begingroup\$

DeltaMax

(Updated to not use files and added a new section. Also modified to no longer get stuck tying in the first section.)

Consists of a couple of strategies which start simple then get more complex — if you clear one, it moves you to the next section.

  • Section 1: Guess {0, 5} consistently
  • Section 2: Check if your last 4 guesses form a constant, linear or quadratic pattern and keep guessing the pattern until it breaks
  • Section 3: Check if you guess an abnormally low amount of some number (less than 1/13) and choose that number
  • Section 4: Analyse bigrams in your choices and look at what's more likely to come out next
  • Section 5: Look at the past 100 rounds and pick the (choice, guess) pair which would have the best expectation, weighted so that recent rounds are more important
  • Section final: Guess randomly, with higher likelihood of having low choices and high guesses. If you get here, then DeltaMax has given up and would like to say, "good game".

To find out which strat was used in the end, uncomment the

if (myChoices.length == 999) { System.out.println(strat); }

line.

Apologies for the horrid Java, I spent my afternoon piecing bits together and relearning the language :)

import java.io.*;
import java.util.ArrayList;
import java.util.Random;
import java.util.Scanner;

public class DeltaMax implements Player
{
    private int strat = 100;

    public String getName() { return "DeltaMax"; }

    public int[] toInts(String s) {
        char [] chars = s.toCharArray();
        int[] ints = new int[chars.length];

        for (int i = 0; i < chars.length; i++){
            ints[i] = Integer.parseInt(Character.toString(chars[i]));
        }

        return ints;
    }

    public int mod6(int n) {
        n = n % 6;
        if (n < 0) { n += 6; }
        return n;
    }

    public int[] getMove(String [] args)
    {
       int[] myChoices = toInts(args[0]);
       int[] myGuesses = toInts(args[1]);
       int myScore = Integer.parseInt(args[2]);
       int[] opponentChoices = toInts(args[3]);
       int[] opponentGuesses = toInts(args[4]);
       int opponentScore = Integer.parseInt(args[5]);

       int rounds = myChoices.length;

       if (rounds == 0) { strat = 100; }
       Random r = new Random();

       // if (myChoices.length == 999) { System.out.println(strat); }

       if (strat == 100) { // Section 1 - {0, 5}
           if (opponentScore - myScore > 21 || (opponentScore >= myScore && rounds > 100)) {
               strat = 200;
           } else {
               return new int[] {0, 5};
           }
       }

       if (strat == 200) { // Section 2 - Mini interpolator
           int w = opponentChoices[opponentChoices.length - 4];
           int x = opponentChoices[opponentChoices.length - 3];
           int y = opponentChoices[opponentChoices.length - 2];
           int z = opponentChoices[opponentChoices.length - 1];

           if (w == x && x == y && y == z) { // Constant
               return new int[] { r.nextInt(4) + 2, w };
           }

           if (mod6(x-w) == mod6(y-x) && mod6(y-x) == mod6(z-y)) { // Linear
               return new int[] { r.nextInt(4) + 2, mod6(z + (z-y)) };
           }

           if (mod6((y-x) - (x-w)) == mod6((z-y) - (y-x))) { // Quadratic
               return new int[] { r.nextInt(4) + 2, mod6((z-y) + mod6((y-x) - (x-w))) };
           }

           strat = 300;
       }

       if (strat == 300) { // Section 3 - exploit least guessed
           int [] counts = new int[6];

           for (int i = 0; i < rounds; i++) {
               counts[opponentGuesses[i]] += 1;
           }

           int minCount = rounds;

           for (int i = 0; i < 6; i++) {
               if ((counts[i] <= 1 || counts[i] * 13 < rounds) && counts[i] < minCount) {
                   minCount = counts[i];
               }
           }

           if (minCount == rounds) {
               strat = 400;
           } else {
               ArrayList<Integer> choices = new ArrayList<Integer>();

               for (int i = 0; i < 6; i++) {
                   if (counts[i] == minCount) {
                       choices.add((Integer) i);
                   }
               }

               int choice = choices.get(r.nextInt(choices.size()));

               // {0, 0} is about the worst thing you can do, so DeltaMax tries to avoid that
               if (choice == 0) {
                   return new int[] { 0, r.nextInt(4) + 2 };
               } else {
                   return new int[] { choice, r.nextInt(6) };
               }
           }
       }

       if (strat == 400) { // Section 4 - bigrams
           if (opponentScore - myScore > 42 || (opponentScore >= myScore && rounds > 300)){
               strat = 500;
           } else {
               int[] opponentScores = new int[6];
               int opponentLast = opponentChoices[opponentChoices.length - 1];

               int[] myScores = new int[6];
               int myLast = myChoices[myChoices.length - 1];

               for (int i = 0; i < opponentChoices.length - 1; i++) {
                   if (opponentChoices[i] == opponentLast) {
                       opponentScores[opponentChoices[i+1]] += 1;
                   }

                   if (myChoices[i] == myLast) {
                       myScores[myChoices[i+1]] += 1;
                   }
               }

               int maxIndex = -1;
               int maxScore = 0;

               int minIndex = -1;
               int minScore = rounds;

               for (int i = 0; i < 6; i++) {
                   if (opponentScores[i] >= maxScore) {
                       maxScore = opponentScores[i];
                       maxIndex = i;
                   }

                   if (myScores[i] <= minScore) {
                       minScore = myScores[i];
                       minIndex = i;
                   }
               }

               if (minIndex == 0 && maxIndex == 0) {
                   return new int[] { 0, r.nextInt(4) + 2 };
               } else {
                   return new int[] { minIndex, maxIndex };
               }
           }
       }

       if (strat == 500) { // Section 5 - best expectation
           if (opponentScore - myScore > 84 || (opponentScore >= myScore && rounds > 800)){
               strat = 573;
           } else {
               int minLen = Math.min(rounds, 100);

               double bestScore = 0;
               int bestGuess = 0;
               int bestChoice = 5;

               for (int guess = 0; guess < 6; guess++) {
                   for (int choice = 0; choice < 6; choice++) {
                       double score = 0;
                       int start = rounds - minLen;

                       for (int i = start; i < rounds; i++) {
                           if (opponentGuesses[i] == choice && opponentChoices[i] != guess) {
                               score -= (choice + opponentChoices[i]) * ((double) i - start) / minLen;
                           } else if (opponentGuesses[i] != choice && opponentChoices[i] == guess) {
                               score += (choice + opponentChoices[i]) * ((double) i - start) / minLen;
                           }
                       }

                       if (score > bestScore) {
                           bestScore = score;
                           bestGuess = guess;
                           bestChoice = choice;
                       }
                   }
               }

               if (bestChoice == 0 && bestGuess == 0) {
                   return new int[] { r.nextInt(4) + 2, bestGuess };
               } else {
                   return new int[] {bestChoice, bestGuess};
               }
           }
       }

       // Section final - hope for the best
       int num = (int) Math.floor(Math.sqrt(r.nextInt(35)));
       return new int[] {5 - num, num};
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ With the current implementation of controller, there's no need to save things in a file if the data is only used for a single game. i.e. private int strat; is good enough. \$\endgroup\$ Feb 12, 2015 at 18:26
  • \$\begingroup\$ @johnchen902 Thanks, I didn't realise I could do that. That makes things a lot easier. \$\endgroup\$
    – Sp3000
    Feb 12, 2015 at 23:48
6
\$\begingroup\$

Historian

(Updated: same logic, shorter code and 100 times faster but you can use only one Historian bot at a tournament.)

Uses weighted random to choose a throw-guess pair based on the effectiveness of using only that pair against opponents previous history. The weights are the squares ot the achievable scores.

public class Historian implements Player {
    private static java.util.Random r = new java.util.Random();
    private static int[] sc=new int[36]; //reseted between games, use only one Historian bot
    public String getName() {return "Historian";}
    public int[] getMove(String [] a) {
        if (a[3].length()==0)  {sc=new int[36]; for(int i=0;i<6;i++) sc[i*6+(5-i)]=5-i;}
        else {int t=a[3].charAt(a[3].length()-1)-'0'; int g=a[4].charAt(a[3].length()-1)-'0';
            for(int i=0; i<6; i++) {sc[i*6+t]+=i+t; sc[g*6+i]-=t+g;}}
        int sum=0; for(int i=0; i<36; i++) {sum+=(sc[i]<1)?1:sc[i]*sc[i];}
        int seed=r.nextInt(sum);int mt=-1;
        while (seed>=0) {seed-=(sc[++mt]<1)?1:sc[mt]*sc[mt];}  
        return new int[] {(int)(mt/6),mt%6};} }

Beats Quinn and Valor (not anymore) and loses to Morra Cowbell. In the tournament with most of the bots Historian comes second to Quinn and Valor.

\$\endgroup\$
3
  • \$\begingroup\$ Well, it's good to see I won on someone's machine. I'm losing the current official leader board. I was wondering it's because of bad luck or some unforeseen subtle bug. \$\endgroup\$ Feb 12, 2015 at 16:38
  • \$\begingroup\$ @johnchen902 I must have hallucinated beating Morra Cowbell. Edited the post. You can delete comments though if they become obsolete. \$\endgroup\$
    – randomra
    Feb 12, 2015 at 17:01
  • \$\begingroup\$ I think I can win 75% of our duel now after my update! \$\endgroup\$ Feb 13, 2015 at 13:04
5
\$\begingroup\$

Extrapolator (v1.1)

Extreme extrapolation from one of the Nash-equilibriums of a simpler game.

I support the terse answer format! (In python-style.)

public class Extrapolator implements Player { 
    private static java.util.Random r = new java.util.Random();
    public String getName() { return "Extrapolator"; }
    public int[] getMove(String [] args) {
        int t=-1;
        for(int c=15,s=r.nextInt(60);s>=0;s-=c,c-=2,t++);
        return new int[] {t,5-t}; } }

Seems to tie with the Magic Cow (Morra Cowbell) and beats other entries I checked.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Please move the Random r to a static field, so you don't initialize it each time, this will help overall performance! \$\endgroup\$
    – Falco
    Feb 12, 2015 at 12:24
  • \$\begingroup\$ Why the change in distribution? \$\endgroup\$ Feb 12, 2015 at 22:54
4
\$\begingroup\$

Trendy

Trendy takes a look at the opponent's past moves, weighting them by recency. Guesses the weightiest, and picks one shifted up slightly from that. Here it is, in all its glory:

public class Trendy implements Player{public String getName(){return "Trendy";}public int[]getMove(String[]a){float h=0,c[]=new float[6];int i=0,l=a[3].length(),p=0;for(;i<l;)c[a[3].charAt(i++)-48]+=(float)i/l;for(i=0;i<6;i++)if(c[i]>h)h=c[p=i];return new int[]{(p+2)%6,p};}}    

The only thing I can compare it with now is Cowbell. It loses by a small margin the majority of the time, but comes out on top often enough for my liking. We'll see how it does with more competitors.

\$\endgroup\$
2
  • 7
    \$\begingroup\$ Could you please format the code onto multiple lines? This isn't code golf... \$\endgroup\$
    – mbomb007
    Feb 11, 2015 at 18:21
  • 7
    \$\begingroup\$ @mbomb007 It takes up less space this way. One of the pains of KotHs in general is all the scrolling to look at entries. I've described what it does, and it's very straightforward for interested parties to format it. \$\endgroup\$
    – Geobits
    Feb 11, 2015 at 18:23
4
\$\begingroup\$

Random Guesser

This is really straight-forward. It effectively rolls a d6, and adds another roll to the previous roll for its guess. It won't win, but it'll provide a nice benchmark.

import java.util.Random;

public class RandomGuesser implements Player {
    private final Random rnd = new Random();
    public String getName() { return "RandomGuesser"; }

    public int[] getMove(String[] args) {
        return new int[] { rnd.nextInt(6), rnd.nextInt(6) };
    }
}
\$\endgroup\$
0
4
\$\begingroup\$

Confused, Python 3

An unnecessarily complicated entry. Even I don't know what it does.

import sys
from random import *

if len(sys.argv) == 7:
    mn,mg,ms,on,og,os = [list(map(int, v)) for v in sys.argv[1:]]
    s,t = sum(mn+on)%5, sum(mg+og)%5
    n = [0]*3+list(range(6))*5+[5,0,5]
    m = [1,0,5,4]+n[:-2:s//9+1]
    numoptions = [n.extend(n[i+s::5+t]+[i]*i*(6+t)) for i in n[:]] and n
    guessoptions = [m.extend(m[i+t//2::8]+[i]*i*(5+s)) for i in m[:]] and m
    num = choice(numoptions)
    guess = choice(guessoptions)
else:
    num, guess = randint(0, 5), randint(0, 5)

sys.stdout.write('%u %u\n' % (num, guess))

Although this advanced algorithm seems to perform worse than random in this tournament, and uses significant memory and run-time, it has stunning results for certain values of 5 ;-)

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

Rainbolt

Takes the difference between the last two numbers our opponent guessed, adds that to our opponent's latest guess, finds the modulus, and avoids choosing that number at all costs. For example, if you guess {5,4,3} (decreasing by one) then we would avoid choosing 2 at all costs.

Takes the difference between the last two numbers our opponent chose, adds that to our opponent's latest choice, and guesses that number. For example, if you guess {1,4,5,2} (increasing by threes) then we would guess 5.

Avoids pointless or very close to pointless rolls.

public class Rainbolt implements Player {
    
    public String getName() { 
        return "Rainbolt"; 
    }

    public int[] getMove(String[] args) {
        int[] yourChoices = toIntArray(args[3]);
        int[] yourGuesses = toIntArray(args[4]);
        
        int myChoice;
        if (yourGuesses.length > 1) {
            int latest = yourGuesses[yourGuesses.length - 1];
            int secondLatest = yourGuesses[yourGuesses.length - 2];
            int numberToAvoid = (2 * latest - secondLatest + 6) % 6;
            do {
                myChoice = rollRandom();
            } while (myChoice == numberToAvoid);
        } else { 
            myChoice = rollRandom();
        }

        int myGuess;
        if (yourChoices.length > 1) {
            int latest = yourChoices[yourChoices.length - 1];
            int secondLatest = yourChoices[yourChoices.length - 2];
            myGuess = (2 * latest - secondLatest + 6) % 6;
        } else { 
            myGuess = rollRandom();
        }
        
        if ((myChoice + myGuess) < 3) {
            do {
                myGuess = rollRandom();
            } while ((myChoice + myGuess) < 3);
        }
        
        return new int[] { myChoice, myGuess };
    }
    
    private static int[] toIntArray(String arg) {
        int[] result = new int[arg.length()];
        for (int i = 0; i < arg.length(); i++)
            result[i] = Character.getNumericValue(arg.charAt(i));
        return result;
    }
    
    private static int rollRandom() {
        return (int) (Math.random() * 6);
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ Don't make your getMove() method static. You can't implement a non-static method like that (at least not in Java 8). \$\endgroup\$
    – GiantTree
    Feb 11, 2015 at 21:38
  • \$\begingroup\$ @GiantTree Thanks for catching that. \$\endgroup\$
    – Rainbolt
    Feb 11, 2015 at 21:43
3
\$\begingroup\$

Evolved Bot

I evolved this bot to be the best random based bot.

import java.util.Arrays;

public class EvolvedBot implements Player {

    private static final double MUTATION_RATE = .2;
    private static final double CROSS_OVER_RATE = .5;

    private final double[] pickProbabilities;
    private final double pickSum;
    private final double[] guessProbabilities;
    private final double guessSum;

    public EvolvedBot(){
        this(new double[]{1.0069058661897903, 0.8949716031797937, 0.5249198534098369, 0.437811964976626, 0.2630925750209125, 0.4862172884617061},
                new double[]{0.6336558074769376, 0.13700756148363913, 0.9586621925124863, 0.11223159366330251, 0.8931390659502754, 0.662974949440039});
    }

    public EvolvedBot(double[] pickProbabilities, double[] guessProbabilities) {
        this.pickProbabilities = pickProbabilities;
        this.guessProbabilities = guessProbabilities;
        pickSum = Arrays.stream(pickProbabilities).sum();
        guessSum = Arrays.stream(guessProbabilities).sum();
    }

    @Override
    public String getName() {
        return "EvolvedBot"/* + ": " + Arrays.toString(pickProbabilities) + Arrays.toString(guessProbabilities)*/;
    }

    @Override
    public int[] getMove(String[] args) {
        int[] move = new int[]{5, 5};
        double pick = Math.random() * pickSum;
        double guess = Math.random() * guessSum;
        for (int i = 0; i < 6; i++){
            if (pick >= 0) {
                pick -= pickProbabilities[i];
                if (pick < 0) {
                    move[0] = i;
                }
            }
            if (guess >= 0){
                guess -= guessProbabilities[i];
                if (guess < 0){
                    move[1] = i;
                }
            }
        }
        return move;
    }

    public EvolvedBot mutate(double mutationRate){
        double[] pickProbabilities = Arrays.copyOf(this.pickProbabilities, 6);
        double[] guessProbabilities = Arrays.copyOf(this.guessProbabilities, 6);

        for (int i = 0; i < 6; i++){
            pickProbabilities[i] = Math.max(pickProbabilities[i] + (Math.random() * 2 - 1) * mutationRate, 0);
        }

        for (int i = 0; i < 6; i++){
            guessProbabilities[i] = Math.max(guessProbabilities[i] + (Math.random() * 2 - 1) * mutationRate, 0);
        }

        return new EvolvedBot(pickProbabilities, guessProbabilities);
    }

}
\$\endgroup\$
0
3
\$\begingroup\$

Popularity, Python 3

Compute guess based on popular numbers used in the past by the opponent. The numbers used recently have more weight. The number choice is often the same as the guess.

import sys
from random import *

if len(sys.argv) == 7:
    mn,mg,ms,on,og,os = [list(map(int, v)) for v in sys.argv[1:]]
    n = list(range(6))
    guess = choice(n + on[-100:] + on[-20:]*8)
    num = choice(n + [guess]*6)
else:
    num, guess = randint(0, 5), randint(0, 5)

sys.stdout.write('%u %u\n' % (num, guess))
\$\endgroup\$
3
\$\begingroup\$

Interpolator

(Switched to Java since Python was causing problems)

Uses polynomial interpolation on the last 10 opponent choices to work out the opponent's next number, then does the same to its own choices and avoids choosing that number. Also, Interpolator has a slight bias against choosing 0 or 5, and its choice is sometimes affected by its guess:

  • If it guesses 0 it will never choose 0
  • If it guesses 5 it will always choose 0 or 1
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

public class Interpolator implements Player
{
    private final int TAIL_LENGTH = 10;

    public String getName()
    {
        return "Interpolator";
    }

    public int[] toInts(String s) {
        char [] chars = s.toCharArray();
        int[] ints = new int[chars.length];

        for (int i = 0; i < chars.length; i++){
            ints[i] = Integer.parseInt(Character.toString(chars[i]));
        }

        return ints;
    }

    public int mod6(int n) {
        n = n % 6;
        if (n < 0) { n += 6; }
        return n;
    }

    public int interpolate(int[] nums){
        boolean allEqual = true;

        for (int i = 0; i < nums.length; i++){
            if (nums[i] != nums[0]){
                allEqual = false;
            }
        }

        if (allEqual) {
            return nums[0];

        } else {
            int [] newNums = new int[nums.length - 1];

            for (int i = 0; i < nums.length - 1; i++){
                newNums[i] = nums[i+1] - nums[i];
            }

            return nums[nums.length - 1] + interpolate(newNums);
        }
    }

    public int[] tail(int[] nums) {
        int minLength = Math.min(TAIL_LENGTH, nums.length);
        int[] tailArray = new int[minLength];

        for (int i = 0; i < minLength; i++){
            tailArray[i] = nums[nums.length - minLength + i];
        }

        return tailArray;
    }

    public int[] getMove(String [] args)
    {
        Random r = new Random();

        if (args[0].length() == 0){
            return new int[] {r.nextInt(5), r.nextInt(5)};
        }

        int[] myChoices = toInts(args[0]);
        int[] opponentChoices = toInts(args[3]);
        int[] opponentGuesses = toInts(args[4]);

        int guess = mod6(interpolate(tail(opponentChoices)));
        int avoid = mod6(interpolate(tail(myChoices)));

        if (guess == 5){ return new int[] {r.nextInt(2), 5}; }

        int[] choiceArray = {0, 1, 1, 2, 2, 3, 3, 4, 4, 5};
        ArrayList<Integer> choices = new ArrayList<Integer>();
        for (int i = 0; i < choiceArray.length; i++) { choices.add(choiceArray[i]); }

        choices.removeAll(Collections.singleton((Integer) avoid));
        if (guess <= 0) { choices.removeAll(Collections.singleton((Integer) 0)); }
        int choice = choices.get(r.nextInt(choices.size())); 

        return new int[] {choice, guess};
    }
}
\$\endgroup\$
3
\$\begingroup\$

CounterBot

Does not counter anyone but rather counts through 0-5 in a circle (0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4 ...)

import java.util.Random;

public class Counter implements Player {

    int lastChoice = new Random().nextInt(6); //Chooses a random starting number

    public String getName() {
        return "CounterBot";
    }

    public int[] getMove(String[] args) {
        int[] oChoices = new int[6]; //Array to store the amount of individual choices of the opponent

        for (int i = 0; i < args[3].length(); i++) {
            int index = Integer.parseInt(String.valueOf(args[3].charAt(i))); //get that choice
            oChoices[index]++; //Increment the number corresponding the choice
        }
        int guess = 0, last = 0;
        for (int i = 0; i < oChoices.length; i++) { //Increment over the choices' array
            if (oChoices[i] > last) { //If the number has been chosen more often than the one before
                last = oChoices[i]; //Set the new maximum value (later the last maximum value)
                guess = i; //Set it as the next guess
            }
        }
        lastChoice++; //Increment our choice
        lastChoice %= 6; //Make sure it's within the bounds of 0-5 ie. modulo 6 (6 modulo 6 = 0)
        return new int[]{lastChoice, guess}; //return our choice and guess
    }
}
\$\endgroup\$
2
\$\begingroup\$

Basilisk, Python

According to legend, The Basilisk is the king of the serpents. (source) I figured that's an appropriate name for a bot that plays "The Noble Game Of Kings" and is written in python. =D This bot strikes fear into the heart of the other bots, and causes death with a single glance.

import sys
import random

args = sys.argv
argc = len(args)
if argc < 6:
    sys.exit()

myChoices = args[1]
myGuesses = args[2]
myScore = args[3]
opponentChoices = args[4]
opponentGuesses = args[5]
opponentScore = args[6]

if len(myChoices) == 0:
    print (random.randint(0, 5))
    print (random.randint(0, 5))
    sys.exit()

guesses = [0, 0, 0, 0, 0, 0]
for char in opponentGuesses:
    i = int(char)
    guesses[i] += 1

#Will default towards smaller guesses to minimize opponent winnings
#For example, if the guess list is
#[5, 3, 7, 3, 4, 8]
#This will return 1. (index of the first 3)
myNextMove = guesses.index(min(guesses))

list = [
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]]
i = 0

while i < len(myGuesses) - 1:
    myGuess = int(myGuesses[i])
    opponentResponse = int(opponentChoices[i+1])
    list[myGuess][opponentResponse] += 1
    i += 1

myPreviousGuess = int(myGuesses[-1])
relevantList = list[myPreviousGuess]

#Defaults towards higher moves.
#For example, if the list is
#[3, 8, 6, 8, 0, 7]
#This will return 3 (index of the last 8)
highestValue = -1
highestIndex = -1
for i in range(len(relevantList)):
    if relevantList[i] >= highestValue:
        highestValue = relevantList[i]
        highestIndex = i


myNextGuess = highestIndex

print (myNextMove)
print (myNextGuess)

This runs on a pretty simple strategy. I'm not expecting it to win, but it was fun to write. This is also my first KoTH challenge, so I'm excited to see how well it performs.

How it picks its next move.

The Basilisk always makes the move that his opponent has guessed the least number of times. In case of a tie, he will pick the smaller number. (to minimize the opponents number of points.)

How it picks its next guess.

The Basilisk will pick the most likely response to its previous guess. For example, If last time, it guessed a 3, it will go back through all of the previous times that it has guessed a 3, and then return the most common opponent move that comes after a guess of 3. In case of a tie, it will pick the larger number (to maximize the number of points it could make.)

On a technical note, will this run correctly? Is print() sufficient, or should I use something like sys.stdout.write() like the other Pythonistas have done?

\$\endgroup\$
6
  • \$\begingroup\$ sys.stdout.write() works in either Python. print() works only in Python 3. It should be okay though. \$\endgroup\$ Feb 13, 2015 at 4:16
  • \$\begingroup\$ No, print() works in either, I'm sure of it. Parentheses are optional in 2.x \$\endgroup\$
    – DJMcMayhem
    Feb 13, 2015 at 4:18
  • \$\begingroup\$ According to this, they work differently. However, the way you use it, it doesn't matter. \$\endgroup\$ Feb 13, 2015 at 4:26
  • \$\begingroup\$ But does that make any difference? \$\endgroup\$
    – DJMcMayhem
    Feb 13, 2015 at 4:29
  • \$\begingroup\$ Apparently not. \$\endgroup\$ Feb 13, 2015 at 4:29
2
\$\begingroup\$

Ditto

This turns into the opponent, but behind by one guess/choice.

import java.util.Random;

public class Ditto implements Player {
    private final Random rnd = new Random();
    public String getName() { return "Ditto"; }

    // myChoices myGuesses myScore oppChoices oppGuesses oppScore
    public int[] getMove(String[] args) {
        if(args[0] == null || args[0].isEmpty()) {
            return new int[] { rnd.nextInt(6), rnd.nextInt(6) };
        }
        int[] myChoices = toIntArray(args[0]);
        int[] myGuesses = toIntArray(args[1]);
        //int myScore = Integer.parseInt(args[2]);
        int[] oppChoices = toIntArray(args[3]);
        int[] oppGuesses = toIntArray(args[4]);
        //int oppScore = Integer.parseInt(args[5]);

        return new int[] { oppChoices[oppChoices.length-1], oppGuesses[oppGuesses.length-1] };
    }

    private static int[] toIntArray(String arg) {
        int[] result = new int[arg.length()];
        for (int i = 0; i < arg.length(); i++)
            result[i] = Character.getNumericValue(arg.charAt(i));
        return result;
    }
}
\$\endgroup\$
1
\$\begingroup\$

NullifierBot, Java

Always throws 0 to minimize any opponent winnings. If the opponent ever guesses my number, they only earn what they threw.

Always guesses 5 to maximize my winnings. Since I can't get any points from my throw, I want to get as many from the opponent. I could randomly guess, but where's the fun in that?

public class NullifierBot implements Player
{
    public String getName()
    {
        return "NullifierBot";
    }

    public int[] getMove(String [] args)
    {
        // always throws 0 to minimize opponents score
        // always guesses 5 to maximize my score
        return new int[] {0, 5}; 
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ I'm guessing this bot will do terribly. Any bot using odds will maybe even get every single guess right after the first. \$\endgroup\$
    – mbomb007
    Feb 13, 2015 at 21:20
  • \$\begingroup\$ @mbomb007 It's not the worst, though! Although it performs worse than your RandomBot. \$\endgroup\$
    – Brian J
    Feb 16, 2015 at 20:18
1
\$\begingroup\$

Erratica, Java

Not great, but it was originally designed to be mostly random, until the value of the trade-off popped out at me. Manages to lose consistently vs. Counter Bot >_<

import java.util.Random;
class Erratica implements Player
{
    private final Random rnd = new Random();

    public String getName() {
        return "Erratica";
    }

    public int[] getMove(String[] args) {
        if(args[0] == null || args[0].isEmpty())
        {
            return new int[]{rnd.nextInt(4)/3+4,rnd.nextInt(4)/3};
        }
        int[] myChoices = toIntArray(args[0]);
        int[] myGuesses = toIntArray(args[1]);
        int myScore = Integer.parseInt(args[2]);
        int[] opponentChoices = toIntArray(args[3]);
        int[] opponentGuesses = toIntArray(args[4]);
        int opponentScore = Integer.parseInt(args[5]);
        int round = opponentChoices.length + 1;
        int choice=0;
        int guess=0;
        if(round<7)
        {
            if(rnd.nextFloat()<(0.1f*(float)round-0.1f))
            {
                choice=(opponentChoices[round-2]+opponentGuesses[round-2])%6;
            }else
            {
                choice=rnd.nextInt(6);
            }
            if(rnd.nextFloat()<(0.1f*(float)round-0.1f))
            {
                guess=opponentChoices[round-2];
            }else
            {
                guess=rnd.nextInt(6);
            }
            return new int[]{choice, rnd.nextInt(6)/5*(5-choice-guess)+guess};
        }else
        {
            int lastError=Math.abs(opponentGuesses[round-2]-myChoices[round-2]);
            for(int i=round-2; i>round-8;i--)
            {
                if(lastError<rnd.nextInt(6))
                {
                    lastError++;
                }else
                {
                    lastError--;
                }
                if(lastError<0)
                    lastError+=6;

            }
            lastError = lastError%6; //shouldn't change
            switch(rnd.nextInt(4))
            {
                case 0:
                    choice=(myChoices[round-2-lastError-round/10])%6;
                    break;
                case 1:
                    choice=(myChoices[lastError+round/10])%6;
                    break;
                default:
                    choice = rnd.nextInt(6);
                    break;
            }

            lastError=Math.abs(myGuesses[round-2]-opponentChoices[round-2]);
            for(int i=round-2; i>round-8;i--)
            {
                if(lastError<rnd.nextInt(6))
                {
                    lastError++;
                }else
                {
                    lastError--;
                }
                if(lastError<0)
                    lastError+=6;
            }
            lastError = lastError%6; //shouldn't change
            switch(rnd.nextInt(4))
            {
                case 0:
                    guess=(opponentChoices[round-2-lastError-round/10])%6;
                    break;
                case 1:
                    guess=(opponentChoices[lastError+round/10])%6;
                    break;
                default:
                    guess = rnd.nextInt(4);
                    break;
            }
        }

        if(myScore>opponentScore)
            switch(rnd.nextInt(2)){
                case 0:
                    choice=5-guess;
                    break;
                case 1:
                    guess=5-choice;
                    break;
                default:
                    break;
            }
        return new int[]{choice, guess};
    }

    private static int[] toIntArray(String arg) {
        int[] result = new int[arg.length()];
        for (int i = 0; i < arg.length(); i++)
            result[i] = Character.getNumericValue(arg.charAt(i));
        return result;
    }
}
\$\endgroup\$
0
1
\$\begingroup\$

Echo, Ruby

mychoices, myguesses, myscore, opponentchoices, opponentguesses, opponentscore = $*

unless mychoices
 puts "0 5"
 exit
end

if mychoices.size > 990 && myscore == '0'
  nextchoice = rand(1..5)
else
  nextchoice = opponentchoices[-1].to_i
end

recentchoices = opponentchoices[/.{0,100}$/]

nextguess = (0..5).max_by do |choice|
  (recentchoices.count(choice.to_s)+1) * (nextchoice + choice)
end

puts "%s %s"%[nextchoice,nextguess]

Plays the last play the opponent made, on the theory that anybody can make a bot that they cannot predict. Guesses based on expectation value using a hundred-move sample.

\$\endgroup\$
4
  • \$\begingroup\$ I'm getting this error: echo.rb:3:in <main>': undefined method size' for nil:NilClass (NoMethodError). It seems to occur only on the first round, when there's no move history. \$\endgroup\$
    – PhiNotPi
    Feb 13, 2015 at 4:06
  • \$\begingroup\$ Odd, didn't happen when I tested. I'll edit. \$\endgroup\$
    – histocrat
    Feb 13, 2015 at 4:38
  • \$\begingroup\$ What is the relevance of the if (mychoices.size > 990 && myscore == '0') nextchoice = rand(1..5) part? \$\endgroup\$
    – randomra
    Feb 16, 2015 at 19:44
  • \$\begingroup\$ If it's about to end up in a scoreless tie (as would happen, for example, against itself), it starts playing randomly, since ~50% chance of a win is better than nothing. \$\endgroup\$
    – histocrat
    Feb 16, 2015 at 20:25
1
\$\begingroup\$

KING FISHER

    import java.util.Random;
public class KingFisher {

    private Random rnd = new Random();
    private int wins = 0;
    private int loses = 0;
    private int median = 0;
    private int medianMoved = 0;
    private int[] weightedLow = {40,65,80,90,95};
    private int[] weightedHigh = {5,15,30,55,95};
    private boolean highWeightMethod = true;
    
    public String getName() {
        return "KingFisher";
    }
    
    public int[] getMove(String [] args)
    {
        char[] mc  = args[0].toCharArray();
        char[] mg  = args[1].toCharArray();
        char[] oc  = args[3].toCharArray();
        char[] og  = args[4].toCharArray();
        int len = mc.length;
        int currentGuess = 0;
        int currentChoice = 0;
        if(len < 10)
            return new int[] {rnd.nextInt(6),rnd.nextInt(6)}; 
        int[] guessWeight = {0,0,0,0,0,0};
        int[] guessWeightTotal = {0,0,0,0,0,0};
        for(int a = 0; a< len;a++)
            guessWeight[oc[a]-48]++;
        if(!highWeightMethod){
            
            int[] whiteList = {1,1,1,1,1,1};
            for(int b = 0;b<3;b++){
                
                int min = 0;
                int max = 0;
                int minIndex = 0;
                int maxIndex = 0;
                for(int a = 0;a<6;a++){
                    
                    if(whiteList[a] == 1){
                        
                        min = guessWeight[a];
                        max = guessWeight[a];
                        minIndex = a;
                        maxIndex = a;
                        break;
                    }
                }
                
                for(int a = 0; a<6;a++){
                    
                    if(whiteList[a] == 1){
                        
                        if(guessWeight[a]<min){
                            
                            min = guessWeight[a];
                            minIndex = a;
                        }
                        if(guessWeight[a]>max){
                            
                            max = guessWeight[a];
                            maxIndex = a;
                        }
                    }
                }
                guessWeight[maxIndex] = min;
                guessWeight[minIndex] = max;
                whiteList[maxIndex] = 0;
                whiteList[minIndex] = 0;
            }
        }

        for(int a = 0; a< 6;a++)
            for(int b = 0; b<=a;b++)
                guessWeightTotal[a]+=guessWeight[b];
        int randInt = rnd.nextInt(guessWeightTotal[5]);
        for(int a = 0; a<6;a++){
            
            if(randInt < guessWeightTotal[a]){
                currentGuess = a;
                break;
            }
        }
        
        if(mg[len-1] == oc[len-1]){
            wins++;
            median++;
        }
        if(og[len-1] == mc[len-1]){
            loses++;
            median--;
        }
        if(median > 2){
            
            medianMoved++;
            median = 0;
        }
        if(median < -2){
            
            medianMoved--;
            median = 0;
        }
        
        randInt = rnd.nextInt(95);
        if((wins-medianMoved)>(loses+medianMoved)){
            
            for(int a = 0; a<6;a++){
                
                if(randInt < weightedLow[a]){
                    currentChoice = a;
                    break;
                }
            }
        }
        else{
            
            for(int a = 0; a<6;a++){
                
                if(randInt < weightedHigh[a]){
                    currentChoice = a;
                    break;
                }
            }
        }
        if(medianMoved < -5){
            
            highWeightMethod = !highWeightMethod;
            medianMoved = 0;
        }
        return new int[] {currentChoice,currentGuess}; 
        
    }
}

This guy consist out of bad guessing alghorithms that use weighted arrays mostly.

\$\endgroup\$
1
  • \$\begingroup\$ Will be in next update. \$\endgroup\$
    – PhiNotPi
    Feb 13, 2015 at 11:49
1
\$\begingroup\$

Uh uh. I know what you're thinking. "Is he gonna pick five or something else?" Well to tell you the truth in all this excitement I'm kinda not sure myself, but being this is a .44 Method, the most powerful method in the world and would overload your stack right away, you've gotta ask yourself one question: "Do I feel lucky?"

Well, do ya, punk?

public class DirtyHarry implements Player {

    @Override
    public String getName() {
        return "DirtyHarry";
    }

    @Override
    public int[] getMove(String[] args) {
        return new int[]{5, 5};
    }
}
\$\endgroup\$

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.