98
\$\begingroup\$

Most Recent Leaderboard @ 2014-08-02 12:00

| Pos # | Author               | Name                    | Language   | Score | Win   | Draw  | Loss  | Avg. Dec. Time |
+-------+----------------------+-------------------------+------------+-------+-------+-------+-------+----------------+
| 1st   | Emil                 | Pony                    | Python2    | 064   | 064   | 000   | 005   | 0026.87 ms     |
| 2nd   | Roy van Rijn         | Gazzr                   | Java       | 062   | 062   | 001   | 006   | 0067.30 ms     |
| 2nd   | Emil                 | Dienstag                | Python2    | 062   | 062   | 001   | 006   | 0022.19 ms     |
| 4th   | ovenror              | TobiasFuenke            | Python2    | 061   | 061   | 001   | 007   | 0026.89 ms     |
| 5th   | PhiNotPi             | BayesianBot             | Perl       | 060   | 060   | 000   | 009   | 0009.27 ms     |
| 6th   | Claudiu              | SuperMarkov             | Python2    | 058   | 058   | 001   | 010   | 0026.77 ms     |
| 7th   | histocrat            | Alternator              | Ruby       | 057   | 057   | 001   | 011   | 0038.53 ms     |
| 8th   | histocrat            | LeonardShelby           | Ruby       | 053   | 053   | 000   | 016   | 0038.55 ms     |
| 9th   | Stretch Maniac       | SmarterBot              | Java       | 051   | 051   | 002   | 016   | 0070.02 ms     |
| 9th   | Martin Büttner       | Markov                  | Ruby       | 051   | 051   | 003   | 015   | 0038.45 ms     |
| 11th  | histocrat            | BartBot                 | Ruby       | 049   | 049   | 001   | 019   | 0038.54 ms     |
| 11th  | kaine                | ExcitingishBot          | Java       | 049   | 049   | 001   | 019   | 0065.87 ms     |
| 13th  | Thaylon              | UniformBot              | Ruby       | 047   | 047   | 001   | 021   | 0038.61 ms     |
| 14th  | Carlos Martinez      | EasyGame                | Java       | 046   | 046   | 002   | 021   | 0066.44 ms     |
| 15th  | Stretch Maniac       | SmartBot                | Java       | 045   | 045   | 001   | 023   | 0068.65 ms     |
| 16th  | Docopoper            | RoboticOboeBotOboeTuner | Python2    | 044   | 044   | 000   | 025   | 0156.55 ms     |
| 17th  | Qwix                 | Analyst                 | Java       | 043   | 043   | 001   | 025   | 0069.06 ms     |
| 18th  | histocrat            | Analogizer              | Ruby       | 042   | 042   | 000   | 027   | 0038.58 ms     |
| 18th  | Thaylon              | Naan                    | Ruby       | 042   | 042   | 004   | 023   | 0038.48 ms     |
| 20th  | Thaylon              | NitPicker               | Ruby       | 041   | 041   | 000   | 028   | 0046.21 ms     |
| 20th  | bitpwner             | AlgorithmBot            | Python2    | 041   | 041   | 001   | 027   | 0025.34 ms     |
| 22nd  | histocrat            | WereVulcan              | Ruby       | 040   | 040   | 003   | 026   | 0038.41 ms     |
| 22nd  | Ourous               | QQ                      | Cobra      | 040   | 040   | 003   | 026   | 0089.33 ms     |
| 24th  | Stranjyr             | RelaxedBot              | Python2    | 039   | 039   | 001   | 029   | 0025.40 ms     |
| 25th  | JoshDM               | SelfLoathingBot         | Java       | 038   | 038   | 001   | 030   | 0068.75 ms     |
| 25th  | Ourous               | Q                       | Cobra      | 038   | 038   | 001   | 030   | 0094.04 ms     |
| 25th  | Ourous               | DejaQ                   | Cobra      | 038   | 038   | 001   | 030   | 0078.31 ms     |
| 28th  | Luis Mars            | Botzinga                | Java       | 037   | 037   | 002   | 030   | 0066.36 ms     |
| 29th  | kaine                | BoringBot               | Java       | 035   | 035   | 000   | 034   | 0066.16 ms     |
| 29th  | Docopoper            | OboeBeater              | Python2    | 035   | 035   | 002   | 032   | 0021.92 ms     |
| 29th  | Thaylon              | NaanViolence            | Ruby       | 035   | 035   | 003   | 031   | 0038.46 ms     |
| 32nd  | Martin Büttner       | SlowLizard              | Ruby       | 034   | 034   | 004   | 031   | 0038.32 ms     |
| 33rd  | Kyle Kanos           | ViolentBot              | Python3    | 033   | 033   | 001   | 035   | 0032.42 ms     |
| 34th  | HuddleWolf           | HuddleWolfTheConqueror  | .NET       | 032   | 032   | 001   | 036   | 0029.86 ms     |
| 34th  | Milo                 | DogeBotv2               | Java       | 032   | 032   | 000   | 037   | 0066.74 ms     |
| 34th  | Timmy                | DynamicBot              | Python3    | 032   | 032   | 001   | 036   | 0036.81 ms     |
| 34th  | mccannf              | YAARBot                 | JS         | 032   | 032   | 002   | 035   | 0100.12 ms     |
| 38th  | Stranjyr             | ToddlerProof            | Java       | 031   | 031   | 010   | 028   | 0066.10 ms     |
| 38th  | NonFunctional User2..| IHaveNoIdeaWhatImDoing  | Lisp       | 031   | 031   | 002   | 036   | 0036.26 ms     |
| 38th  | john smith           | RAMBOBot                | PHP        | 031   | 031   | 002   | 036   | 0014.53 ms     |
| 41st  | EoinC                | SimpleRandomBot         | .NET       | 030   | 030   | 005   | 034   | 0015.68 ms     |
| 41st  | Martin Büttner       | FairBot                 | Ruby       | 030   | 030   | 006   | 033   | 0038.23 ms     |
| 41st  | Docopoper            | OboeOboeBeater          | Python2    | 030   | 030   | 006   | 033   | 0021.93 ms     |
| 44th  | undergroundmonorail  | TheGamblersBrother      | Python2    | 029   | 029   | 000   | 040   | 0025.55 ms     |
| 45th  | DrJPepper            | MonadBot                | Haskel     | 028   | 028   | 002   | 039   | 0008.23 ms     |
| 46th  | Josef E.             | OneBehind               | Java       | 027   | 027   | 007   | 035   | 0065.87 ms     |
| 47th  | Ourous               | GitGudBot               | Cobra      | 025   | 025   | 001   | 043   | 0053.35 ms     |
| 48th  | ProgramFOX           | Echo                    | .NET       | 024   | 024   | 004   | 041   | 0014.81 ms     |
| 48th  | JoshDM               | SelfHatingBot           | Java       | 024   | 024   | 005   | 040   | 0068.88 ms     |
| 48th  | Trimsty              | Herpetologist           | Python3    | 024   | 024   | 002   | 043   | 0036.93 ms     |
| 51st  | Milo                 | DogeBot                 | Java       | 022   | 022   | 001   | 046   | 0067.86 ms     |
| 51st  | William Barbosa      | StarWarsFan             | Ruby       | 022   | 022   | 002   | 045   | 0038.48 ms     |
| 51st  | Martin Büttner       | ConservativeBot         | Ruby       | 022   | 022   | 001   | 046   | 0038.25 ms     |
| 51st  | killmous             | MAWBRBot                | Perl       | 022   | 022   | 000   | 047   | 0016.30 ms     |
| 55th  | Mikey Mouse          | LizardsRule             | .NET       | 020   | 020   | 007   | 042   | 0015.10 ms     |
| 55th  | ja72                 | BlindForesight          | .NET       | 020   | 020   | 001   | 048   | 0024.05 ms     |
| 57th  | robotik              | Evolver                 | Lua        | 019   | 019   | 001   | 049   | 0008.19 ms     |
| 58th  | Kyle Kanos           | LexicographicBot        | Python3    | 018   | 018   | 003   | 048   | 0036.93 ms     |
| 58th  | William Barbosa      | BarneyStinson           | Lua        | 018   | 018   | 005   | 046   | 0005.11 ms     |
| 60th  | Dr R Dizzle          | BartSimpson             | Ruby       | 017   | 017   | 001   | 051   | 0038.22 ms     |
| 60th  | jmite                | IocainePowder           | Ruby       | 017   | 017   | 003   | 049   | 0038.50 ms     |
| 60th  | ArcticanAudio        | SpockOrRock             | PHP        | 017   | 017   | 001   | 051   | 0014.19 ms     |
| 60th  | Dr R Dizzle          | BetterLisaSimpson       | Ruby       | 017   | 017   | 000   | 052   | 0038.23 ms     |
| 64th  | Dr R Dizzle          | LisaSimpson             | Ruby       | 016   | 016   | 002   | 051   | 0038.29 ms     |
| 65th  | Martin Büttner       | Vulcan                  | Ruby       | 015   | 015   | 001   | 053   | 0038.26 ms     |
| 65th  | Dr R Dizzle          | Khaleesi                | Ruby       | 015   | 015   | 005   | 049   | 0038.29 ms     |
| 67th  | Dr R Dizzle          | EdwardScissorHands      | Ruby       | 014   | 014   | 002   | 053   | 0038.21 ms     |
| 67th  | undergroundmonorail  | TheGambler              | Python2    | 014   | 014   | 002   | 053   | 0025.47 ms     |
| 69th  | cipher               | LemmingBot              | Python2    | 011   | 011   | 002   | 056   | 0025.29 ms     |
| 70th  | Docopoper            | ConcessionBot           | Python2    | 007   | 007   | 000   | 062   | 0141.31 ms     |
+-------+----------------------+-------------------------+------------+-------+-------+-------+-------+----------------+
Total Players: 70
Total Matches Completed: 2415
Total Tourney Time: 06:00:51.6877573

Tourney Notes

Excluded Bots

  • BashRocksBot - still no joy with .net execing cygwin bash scripts
  • CounterPreferenceBot - awaiting bug fix
  • RandomlyWeighted - awaiting bug fix
  • CasinoShakespeare - excluded because it requires an active internet connection

Original Posted Question

You've swung around to your friends house for the most epic showdown Battle ever of Rock, Paper, Scissors, Lizard, Spock. In true BigBang nerd-tastic style, none of the players are playing themselves but have created console bots to play on their behalf. You whip out your USB key and hand it over to the Sheldor the Conqueror for inclusion in the showdown. Penny swoons. Or perhaps Howard swoons. We don't judge here at Leonard's apartment.

Rules

Standard Rock, Paper, Scissors, Lizard, Spock rules apply.

  • Scissors cut Paper
  • Paper covers Rock
  • Rock crushes Lizard
  • Lizard poisons Spock
  • Spock smashes Scissors
  • Scissors decapitate Lizard
  • Lizard eats Paper
  • Paper disproves Spock
  • Spock vaporizes Rock
  • Rock crushes Scissors

RPSLV Rules

Each player's bot will play one Match against each other bot in the tournament.

Each Match will consist of 100 iterations of an RPSLV game.

After each match, the winner is the player who has won the most number of games/hands out of 100.

If you win a match, you will be assigned 1 point in the league table. In the result of a draw-match, neither player will gain a point.

Bot Requirements

Your bot must be runnable from the command line.

Sheldor's *nix box has died, so we're running it off his windows 8 Gaming Laptop so make sure your provided solution can run on windows. Sheldor has graciously offered to install any required runtimes (within reason) to be able to run your solution. (.NET, Java, Php, Python, Ruby, Powershell ...)

Inputs

In the first game of each match no arguments are supplied to your bot. In each subsequent game of each match: - Arg1 will contain the history of your bots hands/decisions in this match. - Arg2 will contain the history of your opponents hands/decisions in this match.

History will be represented by a sequence of single capital letters representing the possible hands you can play.

 | R | Rock     |
 | P | Paper    |
 | S | Scissors |
 | L | Lizard   |
 | V | Spock    |

E.g.

  • Game 1: MyBot.exe
  • Game 2: MyBot.exe S V
  • Game 3: MyBot.exe SS VL
  • Game 4: MyBot.exe SSR VLS

Output

Your bot must write a single character response representing his "hand" for each game. The result should be written to STDOUT and the bot should then exit. Valid single capital letters are below.

 | R | Rock     |
 | P | Paper    |
 | S | Scissors |
 | L | Lizard   |
 | V | Spock    |

In the case where your bot does not return a valid hand (i.e. 1 of the above 5 single capital letters, then you automatically forfeit that hand and the match continues.

In the case where both bots do not return a valid hand, then the game is considered a draw and the match continues.

Match Format

Each submitted bot will play one match against each other bot in the tournament.

Each match will last exactly 100 games.

Matches will be played anonymously, you will not have an advanced knowledge of the specific bot you are playing against, however you may use any and all information you can garner from his decision making during the history of the current match to alter your strategy against your opponent. You may also track history of your previous games to build up patterns/heuristics etc... (See rules below)

During a single game, the orchestration engine will run your bot and your opponents bot 100 milliseconds apart and then compare the results in order to avoid any PRNG collisions in the same language/runtime. (this actually happened me during testing).

Judging & Constraints

Dr. Sheldon Cooper in the guise of Sheldor the Conqueror has kindly offered to oversee the running of the tournament. Sheldor the Conqueror is a fair and just overseer (mostly). All decisions by Sheldor are final.

Gaming will be conducted in a fair and proper manner:

  • Your bot script/program will be stored in the orchestration engine under a subfolder Players\[YourBotName]\
  • You may use the subfolder Players\[YourBotName]\data to log any data or game history from the current tournament as it proceeds. Data directories will be purged at the start of each tournament run.
  • You may not access the Player directory of another player in the tournament
  • Your bot cannot have specific code which targets another specific bots behavior
  • Each player may submit more than one bot to play so long as they do not interact or assist one another.

Edit - Additional Constraints

  • Regarding forfeits, they won't be supported. Your bot must play one of the 5 valid hands. I'll test each bot outside of the tournament with some random data to make sure that they behave. Any bots that throw errors (i.e. forfeits errors) will be excluded from the tourney til they're bug fixed.
  • Bots may be derivative so long as they are succinctly different in their behaviour. Bots (including in other languages) that perform exactly the same behaviour as an existing bot will be disqualified
  • There are already spam bots for the following so please don't resubmit
    • Rock - BartSimpson
    • Paper - LisaSimpson
    • Scissor - EdwardScissorhands
    • Spock - Vulcan
    • Lizard - Khaleesi
    • Pseudo Random - SimpleRandomBot & FairBot
    • Psuedo Random RPS - ConservativeBot
    • Psuedo Random LV - Barney Stinson
  • Bots may not call out to 3rd party services or web resources (or anything else which significantly slows down the speed/decision making time of the matches). CasinoShakespeare is the only exception as that bot was submitted prior to this constraint being added.

Sheldor will update this question as often as he can with Tournament results, as more bots are submitted.

Orchestration / Control Program

The orchestration program, along with source code for each bot is available on github.

https://github.com/eoincampbell/big-bang-game

Submission Details

Your submission should include

  • Your Bot's name
  • Your Code
  • A command to
    • execute your bot from the shell e.g.
    • ruby myBot.rb
    • python3 myBot.py
    • OR
    • first compile your both and then execute it. e.g.
    • csc.exe MyBot.cs
    • MyBot.exe

Sample Submission

BotName: SimpleRandomBot
Compile: "C:\Program Files (x86)\MSBuild\12.0\Bin\csc.exe" SimpleRandomBot.cs
Run:     SimpleRandomBot [Arg1] [Arg2]

Code:

using System;
public class SimpleRandomBot
{
    public static void Main(string[] args)
    {
        var s = new[] { "R", "P", "S", "L", "V" };
        if (args.Length == 0)
        {
            Console.WriteLine("V"); //always start with spock
            return;
        }
        char[] myPreviousPlays = args[0].ToCharArray();
        char[] oppPreviousPlays = args[1].ToCharArray();
        Random r = new Random();
        int next = r.Next(0, 5);
        Console.WriteLine(s[next]);
    }
}

Clarification

Any questions, ask in the comments below.

\$\endgroup\$
  • 7
    \$\begingroup\$ What does the history look like when a player has forfeited a hand? \$\endgroup\$ – histocrat Jul 25 '14 at 17:41
  • 1
    \$\begingroup\$ I was going to go all-out with an analytic approach, but most of the bots here are stupid enough to defeat smart AI. \$\endgroup\$ – fluffy Jul 25 '14 at 21:23
  • 1
    \$\begingroup\$ Just because I never am in the lead for any KotH challenge I've competed in, I've taken a screenshot as a memento. \$\endgroup\$ – Kyle Kanos Jul 26 '14 at 2:55
  • 3
    \$\begingroup\$ I'll run another tourney tonight and post the full match results on pastebin... next batch will have about 450 games but should be a bit quicker to run as I've implemented some parallelization stuff in the control prog \$\endgroup\$ – Eoin Campbell Jul 28 '14 at 7:55
  • 3
    \$\begingroup\$ If I'm not mistaken, there seems to be a serious bug in the orchestration script: The histories of player 1 and 2 are always passed to the bots as first and second argument respectively, while according to the rules the bots should always get their own history first. Now player 2 is effectively trying to beat itself. (I got a bit suspicious because my bot won every single match where it was player 1 while losing half of the other matches.) \$\endgroup\$ – Emil Jul 29 '14 at 7:07

79 Answers 79

3
\$\begingroup\$

SuperMarkov, Python

An improved version of the current Markov bot. Makes chains of length 0, 1, and 2, using either only our moves, only his moves, or both, to predict. Weighs the chains and picks what it expects the best result to be. Takes the result less seriously if the sample size is small.

Bot: SuperMarkov
Run: python SuperMarkov.py [Arg1] [Arg2]

Code:

import pprint
import sys
import random

if len(sys.argv) < 2:
    our_hist = opp_hist = "x"
else:
    our_hist = 'x'+sys.argv[1]
    opp_hist = 'x'+sys.argv[2]

hist = zip(opp_hist, our_hist)

def build_chains(length):
    chains = {}
    for typ in ('ours', 'theirs', 'both'):
        chain = {}
        for i in xrange(0, len(hist)-length):
            if typ == 'ours':
                prev = our_hist[i:i+length]
            elif typ == 'theirs':
                prev = opp_hist[i:i+length]
            elif typ == 'both':
                prev = tuple(hist[i:i+length])
            else:
                raise ValueError()
            next = opp_hist[i+length]
            if next == 'x':
                continue

            chain.setdefault(prev, {})
            if next not in chain[prev]:
                chain[prev][next] = 1
            else:
                chain[prev][next] += 1

        #normalize the chain
        # for prev in chain:
        #     total = sum(v for v in chain[prev].values())
        #     for next in list(chain[prev]):
        #         chain[prev][next] /= float(total)

        chains[typ] = chain

    return chains

beats = {
    "S": "LP",
    "P": "VR",
    "R": "SL",
    "L": "VP",
    "V": "SR",
}
beaten_by = {
    "S": "VR",
    "P": "SL",
    "R": "PV",
    "L": "SR",
    "V": "PL",
}

weighted_evs = dict((c, 0) for c in "SPRLV")
chain_weights = {0: 1, 1: 2, 2: 4}
chain_type_weights = {'theirs': 1, 'ours': 1, 'both': 3}

for L in (0, 1, 2):
    chains = build_chains(L)
    for typ, chain in chains.items():
        if typ == 'ours':
            this_prev = our_hist[-L:] if L > 0 else ''
        elif typ == 'theirs':
            this_prev = opp_hist[-L:] if L > 0 else ''
        elif typ == 'both':
            this_prev = tuple(hist[-L:]) if L > 0 else ()

        this_nexts = chain.get(this_prev, {})
        #print typ, this_prev, this_nexts
        sample_size = sum(v for v in this_nexts.values())
        if sample_size == 0:
            continue

        this_weight = chain_weights[L] * chain_type_weights[typ]
        if sample_size < 4:
            this_weight /= 4.0 - sample_size

        for choice in "SVPLR":
            ev = 0
            for which, count in this_nexts.items():
                if which == choice:
                    # push
                    continue
                elif which in beats[choice]:
                    ev += float(count)/sample_size
                elif which in beaten_by[choice]:
                    ev -= float(count)/sample_size
                else:
                    raise ValueError()
            #print "According to %s of length %d, picking %s gives EV %.3f" % (typ, L, choice, ev)
            weighted_evs[choice] += ev * this_weight

#pprint.pprint(weighted_evs)
print max("SPRLV", key=lambda choice: weighted_evs[choice])
\$\endgroup\$
3
\$\begingroup\$

Java - SelfHatingBot

BotName: SelfHatingBot
Compile: Save as 'SelfHatingBot.java'; compile.
Run:     java SelfHatingBot [me] [them]

Bot starts randomly, then always plays a winning move against its own prior move with 50% choice of tactic.

import java.util.Random;

public class SelfHatingBot {

    static final Random RANDOM = new Random();

    private static char randomPlay() {

        switch (RANDOM.nextInt(5)) {

            case 0 : return 'R';

            case 1 : return 'P';

            case 2 : return 'S';

            case 3 : return 'L';

            default : return 'V';
        }
    }

    private static char antiPlay(String priorPlayString) {

        char[] priorPlays = priorPlayString.toCharArray();

        int choice = RANDOM.nextInt(2);

        switch (priorPlays[priorPlays.length - 1]) {

            case 'R' : return choice == 0 ? 'P' : 'V'; 

            case 'P' : return choice == 0 ? 'S' : 'L';

            case 'S' : return choice == 0 ? 'V' : 'R';

            case 'L' : return choice == 0 ? 'R' : 'S';

            default : return choice == 0 ? 'L' : 'P'; // V        
        }
    }

    public static void main(String[] args) {

        char play;

        if (args.length == 0) {

            play = randomPlay();

        } else {

            play = antiPlay(args[0]);
        }

        System.out.print(play);
        return;
    }
}
\$\endgroup\$
3
\$\begingroup\$

CounterPreference bot PHP

First response on this site, lets hope I get it right.

First round it responds randomly. From there it analyses the opponents hand and plans a counter, but if the opponents has a history of predicting this correctly, it will try to avoid falling for that trap again.

Thanks Martin for pointing out my oversight. I have to admit I do not have any experience with command-line and php. But if I understand it correctly it should work as this. Il test it out when I am on a machine I can access the command line with.

Well, this is turning out to be quite educative. Sadly still not able to test. So I apologize if it still does not work.

<?php

$input = $argv;

counterPreferenceBot($input[1], $input[2]);

function counterPreferenceBot($myHandHistory = null, $opponentHandHistory = null)
{


//No Arugments supplied
    if($myHandHistory == null)
    {
        //Random!
        $choices = array("R", "P", "S", "L", "V");
        echo $choices[rand(0, 4)];
    }
    else
    {
        //Arguments supplied
        //Make list of avaiable options
        $r = 0;
        $p = 0;
        $s = 0;
        $l = 0;
        $v = 0;
        foreach($opponentHandHistory as $round => $oppentChoice)
        {
            //Find opponents weaknesses on which it used most.
            if($oppentChoice == "R"){$p++; $v++;}
            else if($oppentChoice == "P"){$s++; $l++;}
            else if($oppentChoice == "S"){$v++; $r++;}
            else if($oppentChoice == "L"){$s++; $r++;}
            else if($oppentChoice == "V"){$l++; $p++;}

            //But avoid falling into whatever clever trap the enemy uses.
            if($myHandHistory[$round] == "R" AND ($oppentChoice == "P" OR $oppentChoice == "V"))
            {   $r--;       
            }else if($myHandHistory[$round] == "P" AND ($oppentChoice == "S" OR $oppentChoice == "L"))
            {   $p--;
            }else if($myHandHistory[$round] == "S" AND ($oppentChoice == "V" OR $oppentChoice == "R"))
            {   $s--;
            }else if($myHandHistory[$round] == "L" AND ($oppentChoice == "S" OR $oppentChoice == "R"))
            {   $l--;
            }else if($myHandHistory[$round] == "V" AND ($oppentChoice == "L" OR $oppentChoice == "P"))
            {   $v--;
            }
        }

        $result = array("R" => $r, "P" => $p, "S" => $s, "L" => $l, "V" => $v);
        //Handsomely stolen code from Micheal Angel to sort those 5 ints into the highest for the right choice.
        $maxvalue = max($result);
        while(list($key,$value)=each($result)){
            if($value==$maxvalue)$maxindex=$key;
        }
        $choice = array("m"=>$maxvalue,"i"=>$maxindex);

        echo $choice['i'];  
    }   
}

?>
\$\endgroup\$
  • 1
    \$\begingroup\$ Welcome to PPCG.SE! For this challenge, you'll need to provide a complete script that can be run from the command line. You might want to append some code to your snippet which reads the command-line arguments and passes them to your function. Then you should also add the corresponding command to run your bot (probably php counterpreferencebot.php). \$\endgroup\$ – Martin Ender Jul 28 '14 at 11:27
  • \$\begingroup\$ @MartinButtner Thanks \$\endgroup\$ – Eoin Campbell Jul 28 '14 at 18:32
  • 1
    \$\begingroup\$ Hey Hikata. Welcome to CodeGolf. I've tried run your script but you'll need to modify it to read from ARGS instead of STDIN. Thanks \$\endgroup\$ – Eoin Campbell Jul 28 '14 at 18:33
3
\$\begingroup\$

LemmingBot

Yep, this is a lemming and his only goal is to die (lose each game). But because he isn't that smart, he always hopes for the other player to make the last move again.

import sys, random

def totallyRandomStarterPick() :
    return "P"    ### Chosen by fair dice roll

picks = { "S" : "PL", "P" : "RV", "R" : "LS", "L" : "VP", "V" : "SR" }
confused = lambda p : random.choice(picks[p])

try :
    opn = sys.argv[-1][-1]
    print(confused(opn))    ### Kill me!
except Exception :
    print(totallyRandomStarterPick())

Written in Python 2.7, run with python LemmingBot.py [args1] [args2] (args1 = own hands, args2 = opponents hands).

Please help him, he really wants to die :(

EDIT: LemmingBot goes mad. Added a bit of randomness for confusing his opponents (and himself).

\$\endgroup\$
  • 2
    \$\begingroup\$ So, I checked the logs, and your suicidal Lemmingbot beat my toddleProof bot 100 - 0 because our patterns exactly coincided. Ouch.... \$\endgroup\$ – Stranjyr Jul 29 '14 at 14:36
  • \$\begingroup\$ That's Brilliant :-) \$\endgroup\$ – Eoin Campbell Jul 30 '14 at 12:37
3
\$\begingroup\$

Uniform (Ruby)

BotName: Uniform
Run:     ruby uniform.rb [Mine] [Theirs]

Code

@counters = { "R" => [ "P", "V" ], "P" => [ "S", "L" ], "S" => [ "V", "R" ], "L" => [ "R", "S" ], "V" => [ "P", "L" ] }
@counts = { "R" => 0, "S" => 0, "P" => 0, "V" => 0, "L" => 0 }

choices = []

if( ARGV != nil && ARGV[0] != nil )

  mine = ARGV[0].split("")
  theirs = ARGV[1].split("")

  c = @counts.clone

  if( mine.length < 50 )
    if( mine.length.even? )
      mine.values_at(* mine.each_index.select {|i| i.even?}).each {|x| c[x]+=1}
      @counts.keys.each {|x| if( c[x]>4 ) then c.delete(x) end}
    else
      mine[0..-1].each_index.select {|i| i.even?}.each {|x| if( mine[x]==mine[-1] ) then c.delete(mine[x+1]) end}
    end
    choices = c.keys
  else
    c2 = @counts.clone
    for i in 0 .. mine.length - 3
      if( mine[i..i+1].join == mine[-2..-1].join ) then c[theirs[i+2]]+=1 end
    end
    bestCount = 0
    @counters.keys.each {|x| @counters[x].each {|y| c2[y]+=c[x];c2[x]+=c[x]*0.4}}
    @counters.keys.each {|x| if( c2[x] > bestCount ) then bestCount = c2[x] end}
    @counters.keys.each {|x| if( c2[x] == bestCount ) then choices.push( x ) end}
  end
else
  choices = @counters.keys
end

puts choices.sample
\$\endgroup\$
  • \$\begingroup\$ bugs sometimes for this. D:/My Software Dev/big-bang-game/BigBang.Orchestrator/bin/Debug/Players/UniformBot/UniformBot.rb:23:in block in <main>': undefined method +' for nil:NilClass (NoMethodError) from D:/My Software Dev/big-bang-game/BigBang.Orchestrator/bin/Debug/Players/UniformBot/UniformBot.rb:22:in each' from D:/My Software Dev/big-bang-game/BigBang.Orchestrator/bin/Debug/Players/UniformBot/UniformBot.rb:22:in <main>' \$\endgroup\$ – Eoin Campbell Jul 28 '14 at 18:23
  • \$\begingroup\$ @EoinCampbell Sorry, should be fixed now ... hopefully ... \$\endgroup\$ – Thaylon Jul 28 '14 at 18:53
3
\$\begingroup\$

Edward Scissorhands

No one had made a Scissor spam bot, so I did.

puts 'S'

Run as

ruby EdwardScissorhands.rb

Daenerys Targaryen

No one had made a Lizards spam bot either, so I did that too. Rains fire down on her enemies.

puts 'L'

Run as

ruby Khaleesi.rb
\$\endgroup\$
  • \$\begingroup\$ :D I almost would have posted exactly that in the beginning of the challenge, but then I went for Vulcan instead. \$\endgroup\$ – Martin Ender Jul 29 '14 at 11:42
  • 1
    \$\begingroup\$ How about Sam Neill/Alan Grant? :P ... There are probably better options though. \$\endgroup\$ – Martin Ender Jul 29 '14 at 11:49
  • 1
    \$\begingroup\$ Yeah. Daenerys is more dragon than lizard. Kaiju maybe ;-) \$\endgroup\$ – Eoin Campbell Jul 29 '14 at 12:51
  • 1
    \$\begingroup\$ @EoinCampbell Carry on complaining and I'll make you run it as DaenerysTargaryenStormbornKhaleesiQueenOfTheAndalsAndTheFirstMenBreakerOfChains.... :P \$\endgroup\$ – Dr R Dizzle Jul 30 '14 at 11:32
  • 1
    \$\begingroup\$ I'd much prefer long names than long run times... god damn casinoshakespeare has an avg. decision time of 0.5s while it calls out to a external url :-) \$\endgroup\$ – Eoin Campbell Jul 30 '14 at 12:39
3
\$\begingroup\$

LoopholeAbuser

I'm withdrawing this bot from the tourney now that the rules have been patched--without the sneakiness, it's basically a slightly worse version of Bart.

@rules = {

  'L' => %w[V P],
  'P' => %w[V R],
  'R' => %w[L S],
  'S' => %w[P L],
  'V' => %w[R S]
}

@moves = @rules.keys

def defeats?(move1, move2)
  @rules[move1].include?(move2)
end

def score(move1, move2)
  if move1 == move2
    0
  elsif defeats?(move1, move2)
    1
  else
    -1
  end
end

def move
  player, opponent = ARGV

  case player.to_s.size
  when 0
    'P'
  when 1
    'R'*100
  when 2
    'L'
  when 3
    'S'
  when 4
    'V'
  else
    extrapolate(player, opponent)
  end

end

def extrapolate(player,opponent)
  likelihoods = Hash.new {0}

  opponent_last_move = opponent[-1]
  @moves.each { |m| likelihoods[m] += opponent.scan(opponent_last_move + m).size }

  my_last_move = player[-1]
  reactions = player.chars.zip(opponent.chars.drop(1))
  @moves.each { |m| likelihoods[m] += reactions.count([my_last_move, m]) }

  @moves.shuffle.max_by do |m|
    likelihoods.map{ |n,c| score(m,n) * c }.reduce(:+)
  end
end

puts move

Just doing these because for once I want to get to the loopholes before other people. If you consider this to be legal (it doesn't throw an error or cause an opponent to throw an error, but it does forfeit a round), run with ruby loophole_abuser.rb.

\$\endgroup\$
  • \$\begingroup\$ :-) haha. nice try. I'll change the controller to validate that it takes only the first character of the first line of STDOUT. Damn you're keeping me on my toes. I gotta refine my KOTH definitions for the next game. Or bring you for a beer first :-) \$\endgroup\$ – Eoin Campbell Jul 28 '14 at 1:27
  • \$\begingroup\$ What does (or did) this do? \$\endgroup\$ – justhalf Jul 30 '14 at 9:34
  • 1
    \$\begingroup\$ It plays a simple Markov-y strategy, except on the second round it plays 100 rocks at once. This was a forfeit, but it showed up in the history fed to the opponent as a hundred separate moves, making me look like BartSimpson. \$\endgroup\$ – histocrat Jul 30 '14 at 13:19
  • \$\begingroup\$ cool... noted & withdrawn \$\endgroup\$ – Eoin Campbell Jul 31 '14 at 17:05
3
\$\begingroup\$

Gazzr

This bot looks at the played games and tries to guess how to beat the enemy:

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

public class Gazzr {

    private static final int DEPTH = 3;
    private final List<String> moves = Arrays.asList(new String[] { "R", "L", "V", "S", "P" });
    private final String[] beatIndex = new String[] { "PV", "RS", "PL", "RV", "LS" };
    private final List<String> loseFromIndex = Arrays.asList(new String[] { "LS", "PV", "RS", "PL", "RV" });
    private final Random random = new Random();

    public static void main(final String[] args) {
        System.out.println(new Gazzr().play(args));;
    }

    public String play(final String[] args) {
        if (args == null || args.length == 0 || args[0].length() < DEPTH) {
            return moves.get(random.nextInt(moves.size()));
        }
        final Map<String, float[]> oppHistDb = new HashMap<String, float[]>();
        final Map<String, float[]> myHistDb = new HashMap<String, float[]>();
        final char[] oppMoves = args[1].toCharArray();
        int i = DEPTH;
        while(i < args[0].length()) {
            updateDB(args[0].substring(i-DEPTH, i), oppMoves[i], myHistDb);
            updateDB(args[1].substring(i-DEPTH, i), oppMoves[i], oppHistDb);
            i++;
        }
        //Predict based on own and opponent moves:
        Integer p1 = predict(args[0].substring(i-DEPTH, i), myHistDb);
        Integer p2 = predict(args[1].substring(i-DEPTH, i), oppHistDb);
        if(p1 == null && p2 == null) {
            return moves.get(random.nextInt(moves.size()));
        }
        if(p2 == null) {
            return "" + beatIndex[p1].charAt(random.nextInt(2));
        } else if(p1 == null) {
            return "" + beatIndex[p2].charAt(random.nextInt(2));
        }
        String s1 = moves.get(p1)+moves.get(p2);
        String s2 = moves.get(p2)+moves.get(p1);
        if(loseFromIndex.contains(s1)) {
            return moves.get(loseFromIndex.indexOf(s1));
        }
        if(loseFromIndex.contains(s2)) {
            return moves.get(loseFromIndex.indexOf(s2));
        }
        if(random.nextBoolean()) {
            return ""+ beatIndex[p1].charAt(random.nextInt(2));
        } else {
            return ""+ beatIndex[p2].charAt(random.nextInt(2));
        }
    }

    private void updateDB(String lastMoves, char nextMove, Map<String, float[]> database) {
        int oppIndex = moves.indexOf("" + nextMove);
        float[] distr = database.get(lastMoves);
        if(distr == null) {
            distr = new float[5];
        }
        distr[oppIndex] += 1.0f;
        database.put(lastMoves, distr);
    }

    public Integer predict(String historicMoves, Map<String, float[]> database) {
        final float[] distr = database.get(historicMoves);
        if(distr == null) {
            return null;
        }
        return predictFromArray(distr);
    }

    private Integer predictFromArray(final float[] distr) {
        float total = 0f;
        for(int i = 0;i<distr.length;i++) {
            total += distr[i];
        }
        float target = total * random.nextFloat();
        total = 0f;
        for(int i = 0;i<distr.length;i++) {
            total += distr[i];
            if(total > target) {
                return i;
            }
        }
        return distr.length - 1;
    }
}

Bug fixes:

  • Tournament round #1: Bot came in last, a bug was causing it to try to lose instead of win
  • (X beats Y, bot predicts opponent plays X, play Y!) #epic
\$\endgroup\$
  • \$\begingroup\$ harsh dude :-) I'll update your source later and include it in the next round. \$\endgroup\$ – Eoin Campbell Aug 1 '14 at 13:32
  • 1
    \$\begingroup\$ I've never really explained how it works...! The biggest advantage this bot has over most RPS-based bots is that it has two separate predictors, one looks at our history, the other on the opponents history. Now instead of picking the best prediction and then choosing the beating strategy I do something else. I leave both predictors to do their job and pick the single RPSLS strategy that beats both predicted moves in case the predictors don't agree (!). \$\endgroup\$ – Roy van Rijn Jan 7 '15 at 16:01
2
\$\begingroup\$

Might As Well Be Random

Yeah, I wouldn't expect much from this guy. Never have I ever written a perl script this long before. That's because I wasn't using half of the code :P

use strict;
use warnings;
use Carp qw(croak);

###Stolen from List::Util::ChooseWeighted (with love)
sub choose_weighted{
    my ($objects, $weightsArg ) = @_;
    my $calcWeight = $weightsArg if 'CODE' eq ref $weightsArg;
    my @weights;        # fix wasteful of memory
    if( $calcWeight){
    @weights =  map { $calcWeight->($_) } @$objects;
    }
    else{
    @weights =@$weightsArg;
    if ( @$objects != @weights ){
        croak "given arefs of unequal lengths!";
    }
    }

    my @ranges = ();        # actually upper bounds on ranges
    my $left = 0;
    for my $weight( @weights){
    $weight = 0 if $weight < 0; # the world is hostile...
    my $right = $left+$weight;
    push @ranges, $right;
    $left = $right;
    }
    my $weightIndex = rand $left;
    for( my $i =0; $i< @$objects; $i++){
    my $range = $ranges[$i];
    return $objects->[$i] if $weightIndex < $range;
    }
}
###Credit to Danny Sadinof for choose_weighted code

sub getmove {
    my (@opponenthistory) = @_;
    if(~~@opponenthistory == 0) {
        return ~~(5*rand());
    }
    my %mapper = qw(R 0 P 1 S 2 V 3 L 4);
    @opponenthistory = map {$mapper{$_}} @opponenthistory;
    my %counts = (
        0 => 0,
        1 => 0,
        2 => 0,
        3 => 0,
        4 => 0);
    $counts{$_}++ for @opponenthistory;
    my @keys = keys %counts;
    my @values = values %counts;
    my $choice = choose_weighted(\@keys, \@values);
    return ($choice + 1) % 4;
}

if(~~@ARGV == 0) {
    print qw/R P S V L/[~~(5*rand())];
    exit;
}
my ($myhistory, $opponenthistory) = @ARGV;

print qw/R P S V L/[getmove(split //, $opponenthistory)];

run as

perl mightaswellberandom.pl
\$\endgroup\$
  • 1
    \$\begingroup\$ Renamed as "MAWBRBot" :-) \$\endgroup\$ – Eoin Campbell Jul 27 '14 at 19:30
2
\$\begingroup\$

Nitpicker (Ruby)

BotName: Nitpicker
Run:     ruby nitpicker.rb [Mine] [Theirs]

Code Update: Now counts more stuff.

Code

scores = [ 0 ] * 9

@moves = { "R" => 0, "P" => 1, "S" => 2, "L" => 3, "V" => 4 }
@responses = [ "R", "P", "S", "L", "V" ]
@counters = { "R" => [ "P", "V" ], "P" => [ "S", "L" ], "S" => [ "V", "R" ], "L" => [ "R", "S" ], "V" => [ "P", "L" ] }

def try( theirs, mine )
  return @counters[theirs].include? mine
end

def counter( move )
  return @counters[move][rand(2)]
end

def follower( move )
  return @responses[(@moves[move]+1)%5]
end

def echo( move )
  return move
end

if ARGV[1] != nil
  for i in 2 .. ARGV[1].length - 1
    points = i > ARGV[1].length - 10 ? 10 - ( ARGV[1].length - i ) : 1
    if try( ARGV[1].split("")[i], counter( ARGV[0].split("")[i-1] ))
      scores[0] += points
    end
    if try( ARGV[1].split("")[i], follower( ARGV[0].split("")[i-1] ))
      scores[1] += points
    end
    if try( ARGV[1].split("")[i], echo( ARGV[0].split("")[i-1] ))
      scores[2] += points
    end
    if try( ARGV[1].split("")[i], counter( ARGV[1].split("")[i-1] ))
      scores[3] += points
    end
    if try( ARGV[1].split("")[i], follower( ARGV[1].split("")[i-1] ))
      scores[4] += points
    end
    if try( ARGV[1].split("")[i], echo( ARGV[1].split("")[i-1] ))
      scores[5] += points
    end
    if try( ARGV[1].split("")[i], counter( ARGV[1].split("")[i-2] ))
      scores[6] += points
    end
    if try( ARGV[1].split("")[i], follower( ARGV[1].split("")[i-2] ))
      scores[7] += points
    end
    if try( ARGV[1].split("")[i], echo( ARGV[1].split("")[i-2] ))
      scores[8] += points
    end
  end
end

bestId = -1;
bestScore = 0;

for j in 0 .. scores.length - 1
  if scores[j] > bestScore
    bestScore = scores[j] 
    bestId = j
  end
end    

if bestId == 0
  puts counter( ARGV[0].split("")[-1] )
elsif bestId == 1
  puts follower( ARGV[0].split("")[-1] )
elsif bestId == 2
  puts echo( ARGV[0].split("")[-1] )
elsif bestId == 3
  puts counter( ARGV[1].split("")[-1] )
elsif bestId == 4
  puts follower( ARGV[1].split("")[-1] )
elsif bestId == 5
  puts echo( ARGV[1].split("")[-1] )
elsif bestId == 6
  puts counter( ARGV[1].split("")[-2] )
elsif bestId == 7
  puts follower( ARGV[1].split("")[-2] )
elsif bestId == 8
  puts echo( ARGV[1].split("")[-2] )
else
  puts @responses[rand(5)]
end
\$\endgroup\$
  • 1
    \$\begingroup\$ Some Ruby tips: • $moves, $responses, and $counters are global variables, but they would make more sense as constants, like MOVES, etc. • The Ruby convention for indent size is 2 spaces (and most other languages use 4 spaces), so 8-space indents for Ruby code are hard to read because they're unusual. • You can simplify the for loop by writing ARGV[1].split("").each_cons do |one_move_ago, two_moves_ago| ... end, which uses Enumerable#each_cons. • Code using $scores would be more readable if the $scores keys were symbols like :counter instead of integers like 0. \$\endgroup\$ – Rory O'Kane Jul 25 '14 at 21:44
  • \$\begingroup\$ Oh well, you can tell that its my first time with ruby ;) Thanks for the pointers, Ill look into that. However, conerning the for loop, mine excludes the zero'th element, your split includes it i believe? \$\endgroup\$ – Thaylon Jul 25 '14 at 22:37
  • \$\begingroup\$ Whoops, I meant to write each_cons(2), not each_cons – and I got the argument order wrong. The loop should be ARGV[1].split("").each_cons(2) do |two_moves_ago, one_move_ago| … end. With that, one_move_ago will never be assigned to the zeroth element of ARGV[1]. As an aside, if you needed to prevent even two_moves_ago from being the zeroth element, you could replace ARGV[1] with ARGV[1][1..-1]. 1..-1 is a range, and when used as an index, it means from the second element 1 to the last element -1. \$\endgroup\$ – Rory O'Kane Jul 25 '14 at 23:07
  • \$\begingroup\$ Some more tips I didn’t have space for in one comment: • Here’s a link to the docs for the aforementioned Enumerable#each_cons. • You can change the global variable $scores to just a local variable scores. It will still be accessible anywhere in the current scope, and since it’s defined at the top level, that means it will be accessible everywhere. • $responses[rand(5)] can be $responses.sample, using Array#sample. \$\endgroup\$ – Rory O'Kane Jul 25 '14 at 23:11
2
\$\begingroup\$

YAAR (Yet Another Arcane Run-time)

If Google's V8 JavaScript Engine is installed (this all depends how far is @eoin-campbell willing to go? ;) )

jsBot.js:

var HAND=["R", "P", "S", "L", "V"];
print(HAND[Math.floor(Math.random() * 5)]);

and run it like so:

d8 jsBot.js

If compiling V8 is too much effort, Node.JS would also be able to run this, provided that print is changed to console.log.

The Spidermonkey jsshell would also work without modifications (precompiled builds on Mozilla's FTP server).

\$\endgroup\$
  • \$\begingroup\$ Sheldor says "Who's this trouble maker"... leave it with me... I'll see if I can coax him round ;0) \$\endgroup\$ – Eoin Campbell Jul 25 '14 at 19:54
  • \$\begingroup\$ I'll set it up with node later. cheers \$\endgroup\$ – Eoin Campbell Jul 28 '14 at 13:25
2
\$\begingroup\$

Herpetologist

This one's in Python 3. He really likes lizards for some reason.

from sys import argv
from random import choice
print("PLSVR"[len(argv)<2 or "RVPSL".index(sorted([choice(argv[2]) for i in range(12)], key=lambda *a:argv[1].count(a[0]))[choice([0, 1])])])
\$\endgroup\$
2
\$\begingroup\$

CasinoShakespeare

Description:

Shakespeare wants to be famous so he feels ok with playing your game. He needs a moment to recall something wise from memory(aka website with quotes) so please don't be too hard on him.

Code:

using System;
using System.Linq;
using System.Net;

namespace CasinoShakespeare
{
    class CasinoShakespeare
    {
        private static void Main()
        {
            try
            {
                char[] ar = {'R', 'P', 'S', 'L', 'V'};
                var a = new WebClient().DownloadString("http://www.iheartquotes.com/api/v1/random").Select(char.ToUpperInvariant).FirstOrDefault(ar.Contains);
                Console.WriteLine(a == default(char) ? 'S' : a);
            }
            catch (Exception)
            {
                Console.WriteLine("R");
            }
        }
    }
}

First compile with: csc.exe CasinoShakespeare.cs then run: CasinoShakespeare.exe

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

Bart

@rules = {

  'L' => %w[V P],
  'P' => %w[V R],
  'R' => %w[L S],
  'S' => %w[P L],
  'V' => %w[R S]
}

@moves = @rules.keys

def defeats?(move1, move2)
  @rules[move1].include?(move2)
end

def score(move1, move2)
  if move1 == move2
    0
  elsif defeats?(move1, move2)
    1
  else
    -1
  end
end

def move
  player, opponent = ARGV

  case player.to_s.size
  when 0..19
    'R'
  when 20..30
    opponent.count('P') > opponent.count('V') ? 'S' : 'L'
  else
    extrapolate(player, opponent)
  end

end

def extrapolate(player,opponent)
  likelihoods = Hash.new {0}

  opponent_last_move = opponent[-1]
  @moves.each { |m| likelihoods[m] += opponent.scan(opponent_last_move + m).size }

  my_last_move = player[-1]
  reactions = player.chars.zip(opponent.chars.drop(1))
  @moves.each { |m| likelihoods[m] += reactions.count([my_last_move, m]) }

  @moves.shuffle.max_by do |m|
    likelihoods.map{ |n,c| score(m,n) * c }.reduce(:+)
  end
end

puts move

Ruby, run with ruby bart.rb. Good old rock, nothing beats it.

\$\endgroup\$
  • \$\begingroup\$ Does this only return rock? \$\endgroup\$ – user8777 Jul 28 '14 at 1:59
  • 1
    \$\begingroup\$ Bart wises up eventually. \$\endgroup\$ – histocrat Jul 28 '14 at 3:39
2
\$\begingroup\$

Alternator

ruby alternator.rb

@rules = {

  'L' => %w[V P],
  'P' => %w[V R],
  'R' => %w[L S],
  'S' => %w[P L],
  'V' => %w[R S]
}

@moves = @rules.keys

def defeats?(move1, move2)
  @rules[move1].include?(move2)
end

def score(move1, move2)
  if move1 == move2
    0
  elsif defeats?(move1, move2)
    1
  else
    -1
  end
end

def move
  player, opponent = ARGV
  i = player.to_s.size
  case
  when i < 5
    @moves[player.to_s.size]
  when i.even?
    @moves.shuffle.min_by{|m|player.count(m)}
  else
    extrapolate(player, opponent)
  end

end

def extrapolate(player,opponent)
  likelihoods = Hash.new {0}

  opponent_last_move = opponent[-1]
  @moves.each { |m| likelihoods[m] += opponent.scan(opponent_last_move + m).size }

  my_last_move = player[-1]
  reactions = player.chars.zip(opponent.chars.drop(1))
  @moves.each { |m| likelihoods[m] += reactions.count([my_last_move, m]) }

  @moves.shuffle.max_by do |m|
    likelihoods.map{ |n,c| score(m,n) * c }.reduce(:+)
  end
end

puts move

On odd numbered moves, tries to predict the opponent. On even numbered moves, tries to be unpredictable.

\$\endgroup\$
  • \$\begingroup\$ you're gonna burn out my machine dude :-) \$\endgroup\$ – Eoin Campbell Jul 28 '14 at 1:35
  • \$\begingroup\$ Heh, sorry. Don't worry, I'm done. \$\endgroup\$ – histocrat Jul 28 '14 at 1:40
  • \$\begingroup\$ kidding man. keep em coming. you're keeping my on my toes :-) \$\endgroup\$ – Eoin Campbell Jul 28 '14 at 1:49
2
\$\begingroup\$

RandomlyWeighted (Ruby)

Bot name: RandomlyWeighted.

Run it with ruby randomly_weighted_rpslv.rb. Tested to work with Ruby versions 1.8.7, 2.0.0, and 2.1.2.

In the first round, this bot randomly decides which of the options it likes most and least – you can think of it as choosing its personality or its priorities. The bot assigns each of the five options a unique probability from the list [0.1, 0.1, 0.2, 0.2, 0.4]. In all rounds after the first, it sticks to those same probabilities when choosing what option to throw.

The bot stores its chosen probabilities in a file priorities.marshal-rb in the Data folder.

randomly_weighted_rpslv.rb
DATA_FOLDER_PATH = begin
  dir_of_this_program = File.expand_path(File.dirname(__FILE__))
  File.join(dir_of_this_program, "Data")
end

module Priorities
  PRIORITIES_FILE_PATH = File.join(DATA_FOLDER_PATH, "priorities.marshal-rb")

  def self.priorities
    begin
      load_saved_priorities
    rescue Errno::ENOENT => e
      # file doesn't exist yet; it's the first round
      chosen_priorities = choose_priorities_randomly
      save_priorities(chosen_priorities)
      chosen_priorities
    rescue IOError => e
      $stderr.puts "Error: priorities file exists, but could not read it."
      $stderr.puts "Fix its permissions or something."
      exit(1)
    end
  end

  private

  def self.choose_priorities_randomly
    probability_spread = [0.1, 0.1, 0.2, 0.2, 0.4]
    options = ['R', 'P', 'S', 'L', 'V']
    paired_options_and_probs = options.shuffle.zip(probability_spread)
    Hash[paired_options_and_probs]
  end

  def self.load_saved_priorities
    File.open(PRIORITIES_FILE_PATH, 'r') do |priorities_file|
      Marshal.load(priorities_file)
    end
  end

  def self.save_priorities(priorities_to_save)
    Dir.mkdir(DATA_FOLDER_PATH)
    File.open(PRIORITIES_FILE_PATH, 'w') do |priorities_file|
      Marshal.dump(priorities_to_save, priorities_file)
    end
  end
end

def prioritized_random_choice(priorities)
  # precondition: the probabilities in `priorities` add up to 1

  remaining_probability_needed = rand
  priorities.to_a.shuffle.each do |option, probability|
    remaining_probability_needed -= probability
    if remaining_probability_needed < 0
      return option
    end
  end
end

puts prioritized_random_choice(Priorities.priorities)
\$\endgroup\$
  • \$\begingroup\$ error when running this one. D:/My Software Dev/big-bang-game/BigBang.Orchestrator/bin/Debug/Players/RandomlyWeighted/RandomlyWeighted.rb:2:in <module:Priorities>': uninitialized constant Priorities::DATA_FOLDER_PATH (NameError) from D:/My Software Dev/big-bang-game/BigBang.Orchestrator/bin/Debug/Players/RandomlyWeighted/RandomlyWeighted.rb:1:in <main>' \$\endgroup\$ – Eoin Campbell Jul 26 '14 at 0:13
2
\$\begingroup\$

GitGudBot

class Progressive
    def main
        args = CobraCore.commandLineArgs
        rng = Random()
        if args.count == 1, Console.write('RPSLV'[rng.next(5)])
        else
            them = List<of char>(args[2].toCharArray)
            me = List<of char>(args[1].toCharArray)
            options = List<of char>()
            for num in me.count
                if '[me[num]][them[num]]' in ['SP','PR','RL','LV','VS','SL','PV','RS','LP','VR'], options.add(me[num])
                else if '[them[num]][me[num]]' in ['SP','PR','RL','LV','VS','SL','PV','RS','LP','VR'], options.add(them[num])
                else, pass
            while options.count > 1
                options.reverse
                if rng.next(3) == 0, break
                else, options.pop
            Console.write(if(options.count,options.last,'RPSLV'[rng.next(5)]))

Written in Cobra, which should be easy to install if you've already got a Visual Studio package on the machine. If not, install any VS package, then install Cobra, then add C:\Cobra\bin to your %PATH%. If you don't feel like changing your %PATH%, simply copy cobra.bat from C:\Cobra\bin to the directory with the source file in it.

Compile & Run: cobra GitGudBot -- [arg1] [arg2]

-OR-

Compile: cobra GitGudBot

Run: GitGudBot [arg1] [arg2]

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

Java - DogeBot

Basically just looks for a pattern of two consecutive hands and beats it. If it can't find a pattern, it just beats whatever the last hand was. And if the number of last hands is less than two or it's a new game, it just prints P.

public class DogeBot {
     public static void main(String []args){

        if (args.length < 2) {
            System.out.println("P");
            return;
        }

        String yourHistory = args[1];  

        if (yourHistory.length() < 2) {
            System.out.println("P");
            return;
        }    

        String possibleHands = "RPSLV";

        for (int i = 0; i < 5; i++) {
            String searchString = "" + possibleHands.charAt(i) + possibleHands.charAt(i);

            if (yourHistory.contains(searchString)) {

                char newHand = possibleHands.charAt((i + 2) % 5);
                System.out.println(newHand == 'S' ? "P" : newHand);

                return;
            } else continue;

        }

        char lastHistoryHand = yourHistory.charAt(yourHistory.length() - 1);
        int i = possibleHands.indexOf(lastHistoryHand);

        char newHand = possibleHands.charAt((i + 2) % 5);
        System.out.println(newHand == 'S' ? "P" : newHand);

        return;
    }
}
\$\endgroup\$
  • \$\begingroup\$ I just realized how dumb my bot is. Once it finds a pattern, it will never play another hand again. Making a 2.0. \$\endgroup\$ – Milo Jul 29 '14 at 3:21
2
\$\begingroup\$

QQ - Cobra

@number float

class Bot

    def main

        index = {c'R':0,c'P':1,c'S':2,c'L':3,c'V':4}
        win = @[@[2,3],@[0,4],@[1,3],@[1,4],@[0,2]]
        lose = @[@[1,4],@[2,3],@[0,4],@[0,2],@[1,3]]
        args = CobraCore.commandLineArgs
        rng = Random()
        path = CobraCore.exePath.split('\\')[:-1].join('\\')+'\\data'

        #ensure data directory exists
        if not Directory.exists(path), Directory.createDirectory(path)

        #read history from the data directory, and check if it's a new tournament
        past_history = [List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5))]
        past_frequency = List<of uint64>(uint64[](5))
        if File.exists(path+'\\history.txt')
            new_game = false
            for num, line in File.readAllLines(path+'\\history.txt').numbered
                for n in 5
                    past_history[num][n] = uint64.parse(line.split[n])
                    past_frequency[n] += uint64.parse(line.split[n])
        else, new_game = true

        #make the move
        this_history = [List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5))]
        this_frequency = List<of uint64>(uint64[](5))
        if new_game, Console.write('RPSLV'[rng.next(5)])
        else if args.count <> 3
            options = List<of float>(float[](5))
            for n in 5
                all_weight = past_frequency[n]/if(.sum(past_frequency),.sum(past_frequency),1)
                for m in win[n]
                    options[m] += all_weight*2
                options[n] += all_weight
                for m in lose[n]
                    options[m] -= all_weight*2
            temp_selection = List<of int>()
            for num, o in options.numbered
                if o == options.sorted.last
                    temp_selection.add(num)
            Console.write('RPSLV'[temp_selection[rng.next(temp_selection.count)]])
        else
            for num, move in args[1].numbered
                this_history[index[move]][index[args[2][num]]] += 1
                this_frequency[index[move]] += 1
                this_frequency[index[args[2][num]]] += 1
            me_last = index[args[1][args[1].length-1]]
            them_last = index[args[2][args[2].length-1]]
            turns = args[1].length
            options = List<of float>(float[](5))
            for n in 5
                this_rchan = this_history[me_last][n]/if(.sum(this_history[me_last]),.sum(this_history[me_last]),1)
                this_rcert = this_history[me_last][n]/(if(this_frequency[me_last],this_frequency[me_last],1)/5f)
                this_weight = this_rchan*this_rcert*turns/10
                all_rchan = past_history[me_last][n]/if(.sum(past_history[me_last]),.sum(past_history[me_last]),1)
                all_rcert = past_history[me_last][n]/(if(past_frequency[me_last],past_frequency[me_last],1)/5f)
                all_weight = all_rchan*all_rcert
                for m in win[n]
                    options[m] -= this_weight*2 + all_weight*2
                options[n] -= this_weight + all_weight
                for m in lose[n]
                    options[m] += this_weight*2 + all_weight*2
            temp_selection = List<of int>()
            for num, o in options.numbered
                if o == options.sorted.last
                    temp_selection.add(num)
            Console.write('RPSLV'[temp_selection[rng.next(temp_selection.count)]])
            past_history[me_last][them_last] += 1

        #update the history file
        record = List<of String>()
        for entry in past_history
            record.add(entry.join(' '))
        File.writeAllLines(path+'\\history.txt',record)

    def sum(list as List<of uint64>) as uint64
        num = 0u64
        for entry in list, num += entry
        return num

Plays against it's best guess of the opponent's last move. Silly QQ...

Compile & Run: cobra QQ -- [arg1] [arg2]

-OR-

Compile: cobra QQ

Run: cobra qq

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

Deja_Q - Cobra

Similar to the first Q, but it doesn't record history of past matches. Fares much better against opponents that attempt to analyse its patterns.

@number float
class Bot
    def main
        index = {c'R':0,c'P':1,c'S':2,c'L':3,c'V':4}
        win = @[@[2,3],@[0,4],@[1,3],@[1,4],@[0,2]]
        lose = @[@[1,4],@[2,3],@[0,4],@[0,2],@[1,3]]
        args = CobraCore.commandLineArgs
        rng = Random()
        if args.count <> 3
            Console.write('RPSLV'[rng.next(5)])
        else if args[2].length < 2
            Console.write('RPSLV'[lose[index[args[2][0]]][rng.next(2)]])
            options = List<of float>(float[](5))
        else
            this_history = [List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5))]
            this_frequency = List<of uint64>(uint64[](5))
            old_history = [List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5))]
            old_frequency = List<of uint64>(uint64[](5)) 
            if args[1].length > 10
                for num, move in args[1][:args[1].length-11].numbered
                    old_history[index[move]][index[args[2][num+1]]] += 1
                    old_frequency[index[args[2][num+1]]] += 1
                for num, move in args[1][args[1].length-11:-1].numbered
                    this_history[index[move]][index[args[2][num+1]]] += 1
                    this_frequency[index[args[2][num+1]]] += 1
            else
                for num, move in args[1][:-1].numbered
                    this_history[index[move]][index[args[2][num+1]]] += 1
                    this_frequency[index[args[2][num+1]]] += 1
            me_last = index[args[1][args[1].length-1]]
            them_last = index[args[2][args[2].length-1]]
            turns = args[1].length
            options = List<of float>(float[](5))
            for n in 5
                this_rchan = this_history[me_last][n]/if(.sum(this_history[me_last]),.sum(this_history[me_last]),1)
                this_rcert = this_history[me_last][n]/(if(this_frequency[me_last],this_frequency[me_last],1)/5f)
                this_weight = this_rchan*this_rcert*2
                old_rchan = old_history[me_last][n]/if(.sum(old_history[me_last]),.sum(old_history[me_last]),1)
                old_rcert = old_history[me_last][n]/(if(old_frequency[me_last],old_frequency[me_last],1)/5f)
                old_weight = old_rchan*old_rcert    
                for m in win[n]
                    options[m] -= this_weight*2 + old_weight*2
                options[n] -= this_weight + old_weight
                for m in lose[n]
                    options[m] += this_weight*2 + old_weight*2
            temp_selection = List<of int>()
            for num, o in options.numbered
                if o == options.sorted.last
                    temp_selection.add(num)
            Console.write('RPSLV'[temp_selection[rng.next(temp_selection.count)]])

    def sum(list as List<of uint64>) as uint64
        num = 0u64
        for entry in list, num += entry
        return num

Compile & Run: cobra Deja_Q -- [arg1] [arg2]

-OR-

Compile: cobra Deja_Q

Run: deja_q [arg1] [arg2]

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

Q - Cobra

Named after the near-omnipotent being(s) in the Star Trek universe, Q plays the long game, makes no mistakes, and watches everything.

For the first game in a tournament run, it will choose a random option.

For the first game in each round, it will choose the option most likely to win against all moves previously played in the tournament. If two options are equal in likelihood, it will choose the least used one. If there is still a tie, it will randomly select from the remaining choices.

For every other game in each round, the following selection algorithm is used:

A = Chance of the current opponent choosing the given move in response to my last move
B = Certainty that the opponent's choices of the given move are dependent on my moves
X = Chance of any opponent choosing the given move in response to my last move
Y = Certainty that any opponent's choices of the given move are dependent on my moves
M = Current game number
N = Win/lose/tie against the given choice (+2,+1,-2)

Move weighting = A*B*N*M/10 + X*Y*N

Breaking ties with the least selected option, and breaking secondary ties with Random()

(uses uint64 in case the round-lengths become rather large)

@number float
class Bot
    def main
        index = {c'R':0,c'P':1,c'S':2,c'L':3,c'V':4}
        win = @[@[2,3],@[0,4],@[1,3],@[1,4],@[0,2]]
        lose = @[@[1,4],@[2,3],@[0,4],@[0,2],@[1,3]]
        args = CobraCore.commandLineArgs
        rng = Random()
        path = CobraCore.exePath.split('\\')[:-1].join('\\')+'\\data'
        if not Directory.exists(path), Directory.createDirectory(path)
        past_history = [List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5))]
        past_frequency = List<of uint64>(uint64[](5))
        if File.exists(path+'\\history.txt')
            new_game = false
            for num, line in File.readAllLines(path+'\\history.txt').numbered
                for n in 5
                    past_history[num][n] = uint64.parse(line.split[n])
                    past_frequency[n] += uint64.parse(line.split[n])
        else, new_game = true
        if new_game, Console.write('RPSLV'[rng.next(5)])
        else if args.count <> 3 or if(args.count<>3,false,args[2].length<2)
            options = List<of float>(float[](5))
            for n in 5
                all_weight = past_frequency[n]/if(.sum(past_frequency),.sum(past_frequency),1)
                for m in win[n]
                    options[m] += all_weight*2
                options[n] += all_weight
                for m in lose[n]
                    options[m] -= all_weight*2
            temp_selection = List<of int>()
            for num, o in options.numbered
                if o == options.sorted.last
                    temp_selection.add(num)
            Console.write('RPSLV'[temp_selection[rng.next(temp_selection.count)]])
        else
            this_history = [List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5))]
            this_frequency = List<of uint64>(uint64[](5))
            old_history = [List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5)),List<of uint64>(uint64[](5))]
            old_frequency = List<of uint64>(uint64[](5)) 
            if args[1].length > 10
                for num, move in args[1][:args[1].length-11].numbered
                    old_history[index[move]][index[args[2][num+1]]] += 1
                    old_frequency[index[args[2][num+1]]] += 1
                for num, move in args[1][args[1].length-11:-1].numbered
                    this_history[index[move]][index[args[2][num+1]]] += 1
                    this_frequency[index[args[2][num+1]]] += 1
            else
                for num, move in args[1][:-1].numbered
                    this_history[index[move]][index[args[2][num+1]]] += 1
                    this_frequency[index[args[2][num+1]]] += 1
            me_last = index[args[1][args[1].length-1]]
            them_last = index[args[2][args[2].length-1]]
            turns = args[1].length
            options = List<of float>(float[](5))
            for n in 5
                this_rchan = this_history[me_last][n]/if(.sum(this_history[me_last]),.sum(this_history[me_last]),1)
                this_rcert = this_history[me_last][n]/(if(this_frequency[me_last],this_frequency[me_last],1)/5f)
                this_weight = (this_rchan*this_rcert)*if(turns>10,2f,turns/5)
                old_rchan = old_history[me_last][n]/if(.sum(old_history[me_last]),.sum(old_history[me_last]),1)
                old_rcert = old_history[me_last][n]/(if(old_frequency[me_last],old_frequency[me_last],1)/5f)
                old_weight = (old_rchan*old_rcert)/if(20-turns>0,20-turns,1)
                all_rchan = past_history[me_last][n]/if(.sum(past_history[me_last]),.sum(past_history[me_last]),1)
                all_rcert = past_history[me_last][n]/(if(past_frequency[me_last],past_frequency[me_last],1)/5f)
                all_weight = (all_rchan*all_rcert)/(Math.log(turns)/1.5f)
                for m in win[n]
                    options[m] -= this_weight*2 + old_weight*2 + all_weight*2
                options[n] -= this_weight + old_weight + all_weight
                for m in lose[n]
                    options[m] += this_weight*2 + old_weight*2 + all_weight*2
            temp_selection = List<of int>()
            for num, o in options.numbered
                if o == options.sorted.last
                    temp_selection.add(num)
            Console.write('RPSLV'[temp_selection[rng.next(temp_selection.count)]])
            past_history[index[args[1][args[1].length-2]]][them_last] += 1

        # write the history file
        record = List<of String>()
        for entry in past_history
            record.add(entry.join(' '))
        File.writeAllLines(path+'\\history.txt',record)

    def sum(list as List<of uint64>) as uint64
        num = 0u64
        for entry in list, num += entry
        return num

Written in Cobra, which should be easy to install if you've already got a Visual Studio package on the machine. If not, install any VS package, then install Cobra, then add C:\Cobra\bin to your %PATH%. If you don't feel like changing your %PATH%, simply copy cobra.bat from C:\Cobra\bin to the directory with the source file in it.

Compile & Run: cobra Q -- [arg1] [arg2]

-OR-

Compile: cobra Q

Run: Q [arg1] [arg2]

NOTE: to update to a newer version of the bot, delete the old executable before compiling

\$\endgroup\$
  • 1
    \$\begingroup\$ +1 for the name, but I have no idea what it does. \$\endgroup\$ – Martin Ender Jul 28 '14 at 8:23
  • \$\begingroup\$ @MartinBüttner Explanation is up now! :) \$\endgroup\$ – Οurous Jul 28 '14 at 8:44
2
\$\begingroup\$

IHaveNoIdeaWhatImDoing - Common Lisp (SBCL-specific)

BotName: IHaveNoIdeaWhatImDoing
Compile: sbcl --load rpsvl.lisp --eval "(sb-ext:save-lisp-and-die \"rpsvl.exe\" :toplevel #'main :executable t)"
Run:     rpsvl.exe [MyArgs] [HisArgs]

Code:

;; Utility ;;
;;;;;;;;;;;;;

(defconstant +possible-moves+ '((paper . #\p) (scissors . #\s) (rock . #\r) (lizard . #\l) (spock . #\v)))

;; Split input string at delim
(defun split-string (string delim)
  (loop for i = 0 then (1+ j)
       as j = (position delim string :start i)
       collect (subseq string i j)
       while j))

(defun get-move (ch)
  (let ((early-move (car (rassoc (char-downcase ch) +possible-moves+))))
    (if (not early-move)
        'forfeit
        early-move)))

;; Take input string and turn into list of symbols for moves
(defun get-moves (string)
  (let ((move-list (coerce string 'list)))
    (loop for ch in move-list
         collect (get-move ch))))

;; Get his moves
(defun get-args ()
  (get-moves (caddar (split-string *posix-argv* #\Space))))

;; Game Logic ;;
;;;;;;;;;;;;;;;;

;; Randomly select move from list
(defun get-random-move ()
  (cdr
   (nth
    (random (list-length +possible-moves+))
    +possible-moves+)))

;; Return list of move counts for input moveset
(defun count-moves (move-set)
  (let ((keys (remove-duplicates move-set)))
    (let ((counts
           (loop for key in keys
                collect (count key move-set))))
      (mapcar #'list keys counts))))

;; Select highest move used from previous moves ignoring ties
(defun max-move (move-list)
  (loop for move in move-list
       with max = (car move-list)
       when (< (cadr max) (cadr move)) do (setf max move)
       finally (return (car max))))

;; Make an educated guess at their next input
(defun parse-input (move-set)
  (let ((move (max-move (count-moves move-set))))
    (cond
      ((eq 'paper    move) #\L)
      ((eq 'scissors move) #\R)
      ((eq 'rock     move) #\V)
      ((eq 'lizard   move) #\S)
      ((eq 'spock    move) #\P)
      (t (get-random-move)))))

;; Main Function ;;
;;;;;;;;;;;;;;;;;;;
(defun main ()
  ;; Reseed RNG
  (setf *random-state* (make-random-state t))
  (let ((input (get-args)))
    (if (< (list-length input) 3)
        (princ (char-upcase (get-random-move)))
        (princ (char-upcase (parse-input input))))))

First time submitting, please let me know if I did something not to expectations. You can compile this just using Steel Bank Common Lisp REPL after loading it or by using buildapp. I can also provide a binary if you want.

Let me know what you guys think, thanks!

\$\endgroup\$
  • \$\begingroup\$ Welcome to Programming Puzzles & Code Golf Stack Exchange. You seem to be doing everything right, though if the OP can't run LISP that binary you mentioned might be necessary, so keep in touch. Have a good time at the site! \$\endgroup\$ – isaacg Jul 29 '14 at 5:48
  • \$\begingroup\$ Thanks for the submission. will have a look at adding lisp support to the orchestration engine this evening. :-) \$\endgroup\$ – Eoin Campbell Jul 29 '14 at 7:52
  • \$\begingroup\$ @EoinCampbell I updated the compile command to be a one liner if that helps you. \$\endgroup\$ – NonFunctional User29916 Jul 30 '14 at 5:00
  • \$\begingroup\$ yep. that worked fine once I figured out I needed to set a %SBCL_HOME% env variable. It's incl. in the current run. \$\endgroup\$ – Eoin Campbell Jul 31 '14 at 20:29
2
\$\begingroup\$

Concession bot - Python 2

The inverse of my other bot. Attempts to predict what move its opponent will play so that it can come up with the worst possible move.

import sys
import random

concessions = {"R": "LS", "P": "VR", "S": "PL", "L": "VP", "V": "SR"}
valid_letters = "RPSLV"

# Create a dictionary of all possible patterns of how the cause history could have affected the effect history's next letter
def generate_pattern_dict(cause_history, effect_history):
    dictionary = {}

    # Run through all the rounds so far
    for round in xrange(len(cause_history)):

        history = cause_history[:round][::-1]
        letter = effect_history[round]

        if not letter in dictionary:
            dictionary[letter] = {}

        # Loop through every size of a string that could fit in the history
        for hs_len in range(1, len(history) + 1):

            # Loop through every position a string of that size could be in
            for hs_pos in range(len(history) - hs_len + 1):

                # Add this occurance to the dictionary; noting how long before the letter it occured and what it was
                history_string = history[hs_pos:hs_pos + hs_len]
                dist = hs_pos
                try:
                    dictionary[letter][(dist, history_string)] += 1
                except KeyError:
                    dictionary[letter][(dist, history_string)] = 1
    return dictionary

# Given a pattern dictionary; predict the next letter based on a history
def get_probabilities(patterns, history):
    probs = {}
    history = history[::-1]

    # Look at all the letters that have been used
    for letter in patterns:
        probs[letter] = 0

        # Look at all the known string patterns to have preceded this string and mark this letter as more likely the more strings that point to it
        for key, occurrences in patterns[letter].iteritems():
            dist, history_string = key

            if history[dist: dist + len(history_string)] == history_string:
                probs[letter] += occurrences

    return probs

def get_most_probable(dictionary):
    highest_prob = 0
    hightst_letters = ""

    for letter, prob in dictionary.iteritems():
        if prob >= highest_prob:
            if prob > highest_prob:
                hightst_letters = letter
                highest_prob = prob
            else:
                hightst_letters += letter

    if len(hightst_letters) == 0:
        hightst_letters = valid_letters

    return random.choice(hightst_letters), highest_prob

def get_histories():
    try:
        return (sys.argv[1], sys.argv[2])
    except IndexError:
        return ("", "")

def concede(letter):
    return random.choice(concessions[letter])

def get_hand(my_history, opponent_history):

    # Look at the affect the opponent's moves have on what they choose next
    pattern_dict = generate_pattern_dict(opponent_history, opponent_history)
    pattern_probs = get_probabilities(pattern_dict, opponent_history)
    pattern_letter, pattern_prob = get_most_probable(pattern_probs)

    # Look at what affect my moves have on what the opponent chooses next
    prediction_dict = generate_pattern_dict(my_history, opponent_history)
    prediction_probs = get_probabilities(prediction_dict, my_history)
    prediction_letter, prediction_prob = get_most_probable(prediction_probs)

    if pattern_prob > prediction_prob:
        opponent_letter = pattern_letter
    elif pattern_prob < prediction_prob:
        opponent_letter = prediction_letter
    else:
        opponent_letter = random.choice((pattern_letter, prediction_letter))

    return concede(opponent_letter)


print get_hand(*get_histories())

Run with:

    python concession_bot.py <args>
\$\endgroup\$
2
\$\begingroup\$

First time poster I'd like to submit the bot I wrote for this tournament:

The Evolver

BotName: Evolver
Compile: 
Run:     lua evolver.lua [StoryEvolver] [StoryOpponent]

and now the code:



-- The Evolver Bot
-- Vittorio "Robotik" Parrella - 01/08/2014
-- Version 3 02/08/2014

--[[
    os.time() gives time in seconds, so the RNG's seed is sometimes 
    the same across many executions, so we use os.clock() to mark
    a time offset (in microseconds.) to set the seed.
]]
function random_seed_microseconds()
    local start = os.clock()
    for i=1,math.random(5000000,40000000) do end -- Loop to let some microseconds pass

    local off = (os.clock() - start)*10000
    start = os.time()*1000

    math.randomseed( start+off )
end

DEBUG = false
DATAFILE = "data.lua"
WIN_WEIGHT  = 1.0
DRAW_WEIGHT = 0.2
LOSE_WEIGHT = 0.5
MAX_FITNESS_VALUE = WIN_WEIGHT + DRAW_WEIGHT

my_moves = arg[1] or ""
opponent_moves = arg[2] or "-"

--[[
  Print debug information
]]
function dprint( ... )
    if DEBUG then
        print( "------" )
        print( ... )
    end
end

--[[
    This bot makes use (kind-of) an evolutionary algorithm, 
    modifying the ranges used to randomly select the next
    symbol to play.

    The fitness function for the data ranges checks if a 
    given set of ranges can create a sequence of symbols
    that is sufficiently good to beat the opponent's plays,
    so that it can be used to actually select a symbol that 
    _could_ beat the next unkwown opponent symbol.
]]
function main()
    random_seed_microseconds()

    -- Initializes the bot.
    gen = new( genetic )

    -- Loads the data from the file or in
    -- case generates new data.
    -- FIX: at the start of a new match throws out the last
    -- datafile.
    if #my_moves < 1 or gen:load( DATAFILE ) == false then
        gen:generate( math.random( 10,20 ) )
    end

    -- Updates the fitness data with the new moves.
    gen:calculate_fitness( my_moves, opponent_moves )

    -- Selects and prints the next symbol using the 
    -- distribution intervals with maximum fitness.
    print( gen:getSymbol( gen.data[1] , opponent_moves ) )

    dprint( gen:print() )

    -- Saves the calculated evolutionary data.
    gen:save( DATAFILE )
end

--[[ 
   Matrix graph for the rules:
   0 = draw
   1 = symbol 1 wins
   2 = symbol 2 wins
 ]]
SYMBOLS = { "R","P","S","L","V" }
rules = {
    ["R"] = { ["R"]=0,["P"]=2,["S"]=1,["L"]=1,["V"]=2 },
    ["P"] = { ["R"]=1,["P"]=0,["S"]=2,["L"]=2,["V"]=1 },
    ["S"] = { ["R"]=2,["P"]=1,["S"]=0,["L"]=1,["V"]=2 },
    ["L"] = { ["R"]=2,["P"]=1,["S"]=2,["L"]=0,["V"]=1 },
    ["V"] = { ["R"]=1,["P"]=2,["S"]=1,["L"]=2,["V"]=0 },
}

dprint( my_moves, opponent_moves )

function limit( min, max, value )
    return math.max( min, math.min( max, value ) )
end

--[[
   Very simple object orientation
]]
function new( class )
    local t = {}

    for i,k in pairs( class ) do
        t[i] = k
    end

    return t
end

--[[
   Class for the genetic algorithm
]]
genetic = {
    -- Prints the rows of paramters in the object + the fitness value for the combination
    print = function( self )
        local str = ""
        for i,fit in pairs( self.data ) do
            str = str.."{ "..table.concat(fit, ",", 1, 5).." } [".. fit[6] .."]\n"
        end
        return str
    end,

    --[[ 
      Writes the bots current data to the file in a format easily
      loadable with Lua.
    ]]
    save = function ( self, filepath )
        local file = io.open( filepath, "w" )
        assert( not(file == nil), "file cannot be opened for write: "..filepath )

        file:write( "return {\n" )
        for _,fit in pairs( self.data ) do
            file:write( "  { " )
            for j=1,#fit do
                file:write( fit[j]..", " )
            end
            file:write( "},\n" )
        end
        file:write( "}\n" )

        file:close()
    end,

    --[[
        Tries to load in memory the evolutionary parameters data table 
        from the data.lua file.
    ]]
    load = function( self, filepath )
        result, self.data = pcall( dofile, filepath )

        if result == false then
            self.data = nil
        end

        return result
    end,

    --[[
        Uses the eveolutionary data row (should always be the max fitness one) to 
        choose the symbol to print using a non-uniform distribution.
    ]]
    getSymbol = function( self, data )
        local roll = math.random()
        for case=1,5 do
            if roll < data[ case ] then
                return SYMBOLS[ case ]
            end
        end
    end,

    --[[
        Calculates the fitness value for all the data rows in the bot
        in respect to the opponent's previous moves.
    ]]
    calculate_fitness = function( self, my_moves, opp_moves )
        local max_fitness = 0
        local performance = 5

        -- Evaluates the performance of the used data row, counting losses
        for i=#(my_moves:sub(-5)), 1,-1 do
            if rules[ my_moves:sub(-i,-i) ][ opp_moves:sub(-i,-i) ] == 2 then
                performance = performance - 1
            end
        end

        for i,row in pairs( self.data ) do
            -- If first row's perfomance is good, there's no need to mutate it.
            if i > 1 or performance < 3 then
                self:mutate( i )

                local wins = 0
                local draws = 0

                --[[
                    Here we basically check if every row would be capable of defeating
                    the last opponent's moves, so that it can be used to MAYBE
                    defeat it's next moves.
                ]]
                local size = #opp_moves
                for piv=size,1,-1 do
                    local sym = self:getSymbol( row ) -- A possible symbol calculated using this row
                    local opp_sym = opp_moves:sub( piv,piv ) -- The last move used by the opponent

                    local result = rules[ sym ][ opp_sym ]

                    if result == 0 then
                        draws = draws + 1
                    elseif result == 1 then
                        wins = wins + 1
                    end
                end

                local winning_state = (wins+draws) / size
                local draws_state = 1.0-(draws / size)
                local losing_state = (size-wins) / size

                --[[ 
                    Updates the fitness of the data row maximising victories+draws ( max 1.0 ).
                    Adds a bonus for the number of draws made ( max 0.4 )
                    Adds a malus for the number of losses ( max 0.4 )
                ]]
                self.data[i][6] = winning_state * WIN_WEIGHT 
                                + draws_state   * DRAW_WEIGHT 
                                - losing_state  * LOSE_WEIGHT

                if self.data[i][6] > max_fitness then
                    max_fitness = self.data[i][6]
                end
                dprint( wins, draws, self.data[i][6] )
            end
        end

        if max_fitness < MAX_FITNESS_VALUE * 0.7 then
            self:generate( math.random( 10, 20 ) )
        else
            -- Orders the rows so that the first row has the highest fitness
            table.sort( self.data, function( a,b ) return a[6] > b[6] end )
        end
    end,

    --[[ Initially generates all the evolutionary data rows ]]
    generate = function( self, size )
        self.data = {}

        for i=1,size do
            self.data[i] = { 0.2, 0.4, 0.6, 0.8 } -- An initial uniform distribution gives good results

            self.data[i][5] = 1 -- High limit for the random symbols intervals
            self.data[i][6] = 0 -- Starting fitness value
        end
    end,

    --[[
        Executes a mutation on the values of a specified data row,
        for now the mutation is just a modification of the values
        to 20% (increase or decrease) of their initial value.
    ]]
    mutate = function( self, index )
        for j=1,4 do
            local plus = 0

            -- randomly picks an increase or a decrease of 20% of the range.
            plus = self.data[index][j] * 0.2

            if math.random() < 0.5 then
                plus = -1 * plus
            end

            self.data[index][j] = limit( 0,1, self.data[index][j] + plus )
        end
        self.data[index][5] = 1 -- the upper limit is always 1

        local tmp = self.data[index][6] -- The fitness value should not be ordered.
        self.data[index][6] = nil
        table.sort( self.data[index] ) -- The values can get mixed and so they're resorted.
        self.data[index][6] = tmp
    end,
}

main()

Should it violate any rule let me know and i'll fix it asap.

\$\endgroup\$
  • \$\begingroup\$ Update to new version 3: * changed the way the fitness value is calculateed * fixed a problem with Lua's RNG * initial generation of data is now uniform I hope in the next tournament it'll fare better than the last one! :D \$\endgroup\$ – robotik Aug 2 '14 at 19:34
  • 1
    \$\begingroup\$ cool. will take a note to update it \$\endgroup\$ – Eoin Campbell Aug 3 '14 at 10:05
2
\$\begingroup\$

The RandomSpockBot, ruby

Simply picks a random move from an array, but maintains a slight bias towards Spock.

puts ['V'].sample
\$\endgroup\$
2
\$\begingroup\$

qeylIs-bortaS - var'aq

I do believe this is the only one in Klingon...

Chooses a random number and 'translates' the number into R, P, S or L. The program is biased against Spock, because Klingons hate Vulcans (I think...).

Run the following shell script (it is in bash, so you might need to translate it to batch):

wget http://www.reocities.com/connorbd/varaq/varaq-current.zip
unzip varaq-current.zip
mv qeylIs-bortaS.vq ./varaq-current
rename varaq-kling varaq-kling.pl

To run the program:

perl varaq-kling.pl < qeylIs-bortaS.vq [arg1] [arg2] | findstr /R /l "^[RPSL]$"

Note: The findstr at the end is to get rid of

var'aq Reference Edition -- Klingon v0.5
(c)2000 Brian Connors, Chris Pressey, and Mark Shoulson

at the start of the program.

Program

(* qeylIs-bortaS.vq *)

6 mIS poD ~ SIq cher (* Generate random number from 0-6 *)

SIq 0 rap'a' { "R" cha' } HIja'chugh
SIq 1 rap'a' { "P" cha' } HIja'chugh
SIq 2 rap'a' { "P" cha' } HIja'chugh
SIq 3 rap'a' { "S" cha' } HIja'chugh
SIq 4 rap'a' { "L" cha' } HIja'chugh
SIq 5 rap'a' { "L" cha' } HIja'chugh
SIq 6 rap'a' { "L" cha' } HIja'chugh

Here's the English version - Kahless' Revenge:

This is just a translation, use qeylIS-bortaS for the tourney.

Run

perl varaq-engl < kahless-revenge.vq [arg1] [arg2] | findstr /R /l "^[RPSL]$"

Code:

6 rand clip ~ index set (* Generate random number from 0-6 *)

index 0 eq? { "R" disp } ifyes
index 1 eq? { "P" disp } ifyes
index 2 eq? { "P" disp } ifyes
index 3 eq? { "S" disp } ifyes
index 4 eq? { "L" disp } ifyes
index 5 eq? { "L" disp } ifyes
index 6 eq? { "L" disp } ifyes
\$\endgroup\$
1
\$\begingroup\$

Java - DogeBot2.0

This is nearly identical to my last submission but it's a little smarter this time. This one only looks at the oppenents last five hands, instead all of them. Also, instead of just printing P when there is less than five hands played, it will beat whatever the last one was.

public class DogeBot {
     public static void main(String []args){

        String possibleHands = "RPSLV";

        if (args.length < 2) {
            System.out.println("P");
            return;
        }

        String yourHistory = args[1];  

        if (yourHistory.length() < 5) {
            char lastHistoryHand = yourHistory.charAt(yourHistory.length() - 1);
            int i = possibleHands.indexOf(lastHistoryHand);

            char newHand = possibleHands.charAt((i + 2) % 5);
            System.out.println(newHand == 'S' ? "P" : newHand);
            return;
        }    

        yourHistory = yourHistory.substring(yourHistory.length() - 5);

        for (int i = 0; i < 5; i++) {
            String searchString = "" + possibleHands.charAt(i) + possibleHands.charAt(i);

            if (yourHistory.contains(searchString)) {

                char newHand = possibleHands.charAt((i + 2) % 5);
                System.out.println(newHand == 'S' ? "P" : newHand);

                return;
            } else continue;

        }

        char lastHistoryHand = yourHistory.charAt(yourHistory.length() - 1);
        int i = possibleHands.indexOf(lastHistoryHand);

        char newHand = possibleHands.charAt((i + 2) % 5);
        System.out.println(newHand == 'S' ? "P" : newHand);

        return;
    }
}
\$\endgroup\$
1
\$\begingroup\$

Excitingish Bot (Java)

Evolved Boring Bot to take a risk and assume everyone else will try to outsmart him by considering his last move.

public class ExcitingishBot
{
    public static void main(String[] args)
    {
        int Rock=0;
        int Paper=0;
        int Scissors=0;
        int Lizard=0;
        int Spock=0;

        if (args.length == 0)
        {
            System.out.print("R");
            return;
        }

        char[] oppPreviousPlays = args[1].toCharArray();
        char[] myPreviousPlays = args[0].toCharArray();

        for (int j=0; j<oppPreviousPlays.length; j++) {
            switch(oppPreviousPlays[j]){
                case 'R': Rock++; break;
                case 'P': Paper++; break;
                case 'S': Scissors++; break;
                case 'L': Lizard++; break;
                case 'V': Spock++;
            }
        }

        if (myPreviousPlays.length()>2){
        for (int j=1; j<3; j++) {
            switch(myPreviousPlays[myPreviousPlays.length()-j]){
                case 'R': Paper+=5; Spock+=5; break;
                case 'P': Scissors+=5; Lizard+=5; break;
                case 'S': Rock+=5; Spock+=5; break;
                case 'L': Scissors+=5; Rock+=5; break;
                case 'V': Paper+=5; Lizard+=5;
              }
        }}

        int Best = Math.max(Math.max(Lizard+Scissors-Spock-Paper,
                                     Rock+Spock-Lizard-Scissors),
                            Math.max(Math.max(Paper+Lizard-Spock-Rock,
                                              Paper+Spock-Rock-Scissors),
                                     Rock+Scissors-Paper-Lizard));

        if (Best== Lizard+Scissors-Spock-Paper){
            System.out.print("R"); return;
        } else if (Best== Rock+Spock-Lizard-Scissors){
            System.out.print("P"); return;
        } else if (Best== Paper+Lizard-Spock-Rock){
            System.out.print("S"); return;
        } else if(Best== Paper+Spock-Rock-Scissors){
            System.out.print("L"); return;
        } else {
            System.out.print("V"); return;
        }
    }
}
\$\endgroup\$
1
\$\begingroup\$

Botzinga - Java

BotName: Botzinga
Compile: Save as Botzinga.java and compile as usual
Run:     Botzinga [Arg1] [Arg2]    

The code:

public class Botzinga {

private static int curTurn = 0;
private static char[] opHand;
private static char[] myHand;

public static void main(String[] args) {

    if(args.length != 0) {
        int curTurn = args[0].length();
        opHand = args[1].toCharArray();
        myHand = args[0].toCharArray();
    }
    char c = ' ';

    if(curTurn >= 5) {
        if(opHand[curTurn-1] == opHand[curTurn-2] && opHand[curTurn-1] == opHand[curTurn-3])
            c = beater(opHand[curTurn - 1]);
        else {
            if (beats(myHand[curTurn - 1], opHand[curTurn - 1]))
                c = beater(myHand[curTurn - 1]);
            else
                c = beater(opHand[curTurn - 1]);
        }

    }
    else
        c = playRandom();
    System.out.print(c);

}


public static char playRandom() {
    char c = ' ';
    switch((int)(Math.random()*5)) {
        case 0: c = 'R'; break;
        case 1: c = 'P'; break;
        case 2: c = 'S'; break;
        case 3: c = 'L'; break;
        case 4: c = 'V'; break;
    }
    return c;
}

private static char beater(char a) {
    int Rock=0;
    int Paper=0;
    int Scissors=0;
    int Lizard=0;
    int Spock=0;

    for (int j=0; j<curTurn; j++) {
        switch(myHand[j]){
            case 'R': Rock++; break;
            case 'P': Paper++; break;
            case 'S': Scissors++; break;
            case 'L': Lizard++; break;
            case 'V': Spock++;
        }
    }

    switch (a) {
        case 'R': return Paper < Spock ? 'P' : 'V';
        case 'P': return Scissors < Lizard ? 'S' : 'L';
        case 'S': return Rock < Spock ? 'R' : 'V';
        case 'V': return Paper < Lizard ? 'P' : 'L';
        case 'L': return Rock < Scissors ? 'R' : 'S';
        default: return ' ';
    }

}

private static boolean beats(char a, char b) {
    boolean win = false;
    switch (a) {
        case 'R':
            win = b == 'S' || b == 'L';
            break;
        case 'P':
            win = b == 'R' || b == 'V';
            break;
        case 'S':
            win = b == 'P' || b == 'L';
            break;
        case 'V':
            win = b == 'S' || b == 'R';
            break;
        case 'L':
            win = b == 'V' || b == 'P';
            break;
    }
    return win;
}
}
\$\endgroup\$
1
\$\begingroup\$

Robotic Oboe Bot Oboe Tuner - Python 2

Goes through each letter its opponent has submitted and looks for any recurring strings in both it and its opponent's history before that point.

Every possible contiguous string before the letter it's checking will be noted in a dictionary along with the distance that string appeared from the letter being checked. This dictionary persists through each letter that is checked and each time the same substring appears the same distance from the letter being checked a counter for that substring in the dictionary is incremented. This process is done separately for both the opponent's history + the opponent's chosen letter and this bot's history + the opponent's chosen letter.

Once each pattern dictionary has been assembled; the bot goes through each letter that was used by the opponent and compares the patterns to the current history. If there is a match with the correct distance from the end then the letter being tested gets a point for every occurrence that substring had had. If the pattern is repeating then the letter will get points separately for each time it had appeared before that as each distance + string combo is considered a distinct dictionary entry.

Once the most probable patternistic next move has been determined - a random countering move is chosen for that move.

In the case where there is a tie; one of the letters in the tie is picked at random. When there are no patterns to work off (before the opponent has repeated any letters) - the bot picks randomly.

The cool thing about this implementation is that because the letters get a flat score for each pattern detected; more recent characters tend to have more weight than older characters as they will have more patterns preceding them and longer patterns will have exponentially more weight as the sub-patterns within them will also be counted as separate patterns. This bot should even deal with bots that periodically alternate strategies once it figures out the pattern.

import sys
import random

counters = {"R": "VP", "P": "SL", "S": "VR", "L": "RS", "V": "LP"}
valid_letters = "RPSLV"

# Create a dictionary of all possible patterns of how the cause history could have affected the effect history's next letter
def generate_pattern_dict(cause_history, effect_history):
    dictionary = {}

    # Run through all the rounds so far
    for round in xrange(len(cause_history)):

        history = cause_history[:round][::-1]
        letter = effect_history[round]

        if not letter in dictionary:
            dictionary[letter] = {}

        # Loop through every size of a string that could fit in the history
        for hs_len in range(1, len(history) + 1):

            # Loop through every position a string of that size could be in
            for hs_pos in range(len(history) - hs_len + 1):

                # Add this occurance to the dictionary; noting how long before the letter it occured and what it was
                history_string = history[hs_pos:hs_pos + hs_len]
                dist = hs_pos
                try:
                    dictionary[letter][(dist, history_string)] += 1
                except KeyError:
                    dictionary[letter][(dist, history_string)] = 1
    return dictionary

# Given a pattern dictionary; predict the next letter based on a history
def get_probabilities(patterns, history):
    probs = {}
    history = history[::-1]

    # Look at all the letters that have been used
    for letter in patterns:
        probs[letter] = 0

        # Look at all the known string patterns to have preceded this string and mark this letter as more likely the more strings that point to it
        for key, occurrences in patterns[letter].iteritems():
            dist, history_string = key

            if history[dist: dist + len(history_string)] == history_string:
                probs[letter] += occurrences

    return probs

def get_most_probable(dictionary):
    highest_prob = 0
    hightst_letters = ""

    for letter, prob in dictionary.iteritems():
        if prob >= highest_prob:
            if prob > highest_prob:
                hightst_letters = letter
                highest_prob = prob
            else:
                hightst_letters += letter

    if len(hightst_letters) == 0:
        hightst_letters = valid_letters

    return random.choice(hightst_letters), highest_prob

def get_histories():
    try:
        return (sys.argv[1], sys.argv[2])
    except IndexError:
        return ("", "")

def counter(letter):
    return random.choice(counters[letter])

def get_hand(my_history, opponent_history):

    # Look at the affect the opponent's moves have on what they choose next
    pattern_dict = generate_pattern_dict(opponent_history, opponent_history)
    pattern_probs = get_probabilities(pattern_dict, opponent_history)
    pattern_letter, pattern_prob = get_most_probable(pattern_probs)

    # Look at what affect my moves have on what the opponent chooses next
    prediction_dict = generate_pattern_dict(my_history, opponent_history)
    prediction_probs = get_probabilities(prediction_dict, my_history)
    prediction_letter, prediction_prob = get_most_probable(prediction_probs)

    if pattern_prob > prediction_prob:
        opponent_letter = pattern_letter
    elif pattern_prob < prediction_prob:
        opponent_letter = prediction_letter
    else:
        opponent_letter = random.choice((pattern_letter, prediction_letter))

    return counter(opponent_letter)


print get_hand(*get_histories())

Run with:

    python robotic_oboe_bot_oboe_tuner.py <args>
\$\endgroup\$
  • \$\begingroup\$ I assume that's you SF ;-) damn long bot name broke my grid formatter :-) \$\endgroup\$ – Eoin Campbell Jul 29 '14 at 18:28

Your Answer

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

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