101
\$\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\$
58
  • 7
    \$\begingroup\$ What does the history look like when a player has forfeited a hand? \$\endgroup\$
    – histocrat
    Commented Jul 25, 2014 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
    Commented Jul 25, 2014 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
    Commented Jul 26, 2014 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\$ Commented Jul 28, 2014 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
    Commented Jul 29, 2014 at 7:07

79 Answers 79

29
\$\begingroup\$

Pony (Python 2)

This is based on a rock-paper-scissors bot I wrote some time ago for a programming challenge at the end of a Udacity online class. I changed it to include Spock and lizard and made some improvements.

The program has 11 different simple strategies, each with 5 variants. It chooses from among these based on how well they would have performed over the last rounds.

I removed a fallback strategy that just played random against stronger opponents. I guess it's more fun like this.

import sys

# just play Spock for the first two rounds
if len(sys.argv)<2 or len(sys.argv[1])<2: print 'V'; sys.exit()

# initialize and translate moves to numbers for better handling:
my_moves, opp_moves = sys.argv[1], sys.argv[2]
moves = ('R', 'P', 'S', 'V', 'L')   
history = zip([moves.index(i) for i in my_moves],
              [moves.index(i) for i in opp_moves])

# predict possible next moves based on history
def prediction(hist):
    N = len(hist)    

    # find longest match of the preceding moves in the earlier history
    cand_m = cand_o = cand_b = range(N-1)
    for l in xrange(1,min(N, 20)):
        ref = hist[N-l]
        cand_m = ([c for c in cand_m if c>=l and hist[c-l+1][0]==ref[0]]
                  or cand_m[-1:])
        cand_o = ([c for c in cand_o if c>=l and hist[c-l+1][1]==ref[1]]
                  or cand_o[-1:])
        cand_b = ([c for c in cand_b if c>=l and hist[c-l+1]==ref]
                  or cand_b[-1:])

    # analyze which moves were used how often
    freq_m, freq_o = [0]*5, [0]*5
    for m in hist:
        freq_m[m[0]] += 1
        freq_o[m[1]] += 1

    # return predictions
    return ([hist[-i][p] for i in 1,2 for p in 0,1]+   # repeat last moves
            [hist[cand_m[-1]+1][0],     # history matching of my own moves
             hist[cand_o[-1]+1][1],     # history matching of opponent's moves
             hist[cand_b[-1]+1][0],     # history matching of both
             hist[cand_b[-1]+1][1],
             freq_m.index(max(freq_m)), # my most frequent move
             freq_o.index(max(freq_o)), # opponent's most frequent move
             0])                        # good old rock (and friends)


# what would have been predicted in the last rounds?
pred_hist = [prediction(history[:i]) for i in xrange(2,len(history)+1)]

# how would the different predictions have scored?
n_pred = len(pred_hist[0])
scores = [[0]*5 for i in xrange(n_pred)]
for pred, real in zip(pred_hist[:-1], history[2:]):
    for i in xrange(n_pred):
        scores[i][(real[1]-pred[i]+1)%5] += 1
        scores[i][(real[1]-pred[i]+3)%5] += 1
        scores[i][(real[1]-pred[i]+2)%5] -= 1
        scores[i][(real[1]-pred[i]+4)%5] -= 1

# return best counter move
best_scores = [list(max(enumerate(s), key=lambda x: x[1])) for s in scores]
best_scores[-1][1] *= 1.001   # bias towards the simplest strategy    
if best_scores[-1][1]<0.4*len(history): best_scores[-1][1] *= 1.4
strat, (shift, score) = max(enumerate(best_scores), key=lambda x: x[1][1])
print moves[(pred_hist[-1][strat]+shift)%5]

Run as:

python Pony.py

Edit: I made a small change by putting in a bias towards the simplest strategy (i.e. always play the same move) in unsure cases. This helps a bit to not try to find overly complicated patterns where there are none, e.g. in bots like ConservativeBot.

Note: I tried to explain the basic history matching strategy that this bot uses in the post for my other bot Dienstag.

\$\endgroup\$
3
  • 3
    \$\begingroup\$ A 96 percent win ratio is outstanding. \$\endgroup\$
    – AndoDaan
    Commented Jul 31, 2014 at 0:56
  • \$\begingroup\$ Very nice. You may like Iocaine Powder, if you haven't seen it already. \$\endgroup\$
    – wchargin
    Commented Jul 31, 2014 at 5:14
  • \$\begingroup\$ @WChargin, of course. :) When I wrote my original code, I had read about Iocaine Powder some years earlier and vaguely remembered the general idea. So, Pony is indeed inspired by it, if not very directly. As it turns out, they are very similar. I think mine has a wider repertoire of strategies while Iocaine Powder has a clever level of meta-meta reasoning that I did not include. \$\endgroup\$
    – Emil
    Commented Jul 31, 2014 at 6:00
20
\$\begingroup\$

ConservativeBot, Ruby

New things are bad things.

puts ['R','P','S'].sample

Run like

ruby conservative.rb
\$\endgroup\$
1
  • \$\begingroup\$ OG version is the best version. \$\endgroup\$
    – maxywb
    Commented Jul 25, 2014 at 16:25
20
\$\begingroup\$

Markov, Ruby

Looks at the opponent's last two moves and determines the possible (and most likely) follow ups. If the combination hasn't been picked before, he just uses all of the opponent's moves (so far) instead. Then he collects all the possible responses for these and picks a random one.

responses = {
  'R' => ['P', 'V'],
  'P' => ['S', 'L'],
  'S' => ['R', 'V'],
  'L' => ['S', 'R'],
  'V' => ['P', 'L']
}

if ARGV.length == 0 || (history = ARGV[1]).length < 3
    choices = ['R','P','S','L','V']
else
    markov = Hash.new []
    history.chars.each_cons(3) { |chars| markov[chars[0..1].join] += [chars[2]] }

    choices = []
    likely_moves = markov.key?(history[-2,2]) ? markov[history[-2,2]] : history.chars
    likely_moves.each { |move| choices += responses[move] }
end

puts choices.sample

Run like

markov.rb
\$\endgroup\$
3
  • \$\begingroup\$ And then I use this program to determine the most possible move that I will do next then find out what you will do and finally find a way to beat what you will do and infinite-loop the whole thing again, again and again. \$\endgroup\$
    – Jamie
    Commented Jul 29, 2014 at 0:35
  • \$\begingroup\$ @Jamie You mean like this guy? codegolf.stackexchange.com/a/35295/8478 \$\endgroup\$ Commented Jul 29, 2014 at 6:51
  • \$\begingroup\$ you guess it. (the comment was not long enough to be posted) \$\endgroup\$
    – Jamie
    Commented Jul 29, 2014 at 23:22
14
\$\begingroup\$

Star Wars Fan - Ruby

Screw you, Spock

puts ['R','P','L','S'].sample

Run it like:

ruby starwarsfan.rb
\$\endgroup\$
5
  • \$\begingroup\$ Added to Controller \$\endgroup\$ Commented Jul 25, 2014 at 17:50
  • \$\begingroup\$ you can rollback by answer edit - I'll just comment here when I've added them. \$\endgroup\$ Commented Jul 25, 2014 at 17:50
  • \$\begingroup\$ Why R and S? :P \$\endgroup\$
    – cjfaure
    Commented Jul 26, 2014 at 13:48
  • \$\begingroup\$ @mardavi It is a Star Wars fan because it does not use Spock. \$\endgroup\$ Commented Jul 28, 2014 at 10:47
  • \$\begingroup\$ ah, you are right (of course). I read it too fast, my mistake (but without consequences luckily) \$\endgroup\$
    – mardavi
    Commented Jul 28, 2014 at 11:05
13
\$\begingroup\$

Barney Stinson - Lua

I only have one rule: New is always better. Screw old Jo Ken Po or whatever you call it.

math.randomseed(os.time())
print(math.random() > 0.5 and "V" or "L")

Run it like:

lua legenwaitforitdary.lua
\$\endgroup\$
9
\$\begingroup\$

IocainePowder, Ruby

enter image description here

Based off (shamelessly stolen from) the RPS strategy here. The bot looks chooses a guess identical to the Markov bot, but then assumes the opponent has guessed what it's going to choose, and chooses a move to beat that one accordingly.

Note that I've just adapted the basic idea of the linked strategy, not followed it in detail.

responses = {
  'R' => ['P', 'V'],
  'P' => ['S', 'L'],
  'S' => ['R', 'V'],
  'L' => ['S', 'R'],
  'V' => ['P', 'L']
}

if ARGV.length == 0 || (history = ARGV[1]).length < 3
    choices = ['R','P','S','L','V']
else
    markov = Hash.new []
    history.chars.each_cons(3) { |chars| markov[chars[0..1].join] += [chars[2]] }

    choices = []
    likely_moves = markov.key?(history[-2,2]) ? markov[history[-2,2]] : history.chars
    likely_moves.each { |move| choices += responses[move] }
end

myChoice = choices.sample 
theirChoice = responses[myChoice].sample
actualChoice = responses[theirChoice].sample
puts actualChoice

Run like

iocaine.rb
\$\endgroup\$
3
  • 6
    \$\begingroup\$ You keep using that word. I do not think it means what you think it means. \$\endgroup\$
    – JoshDM
    Commented Jul 28, 2014 at 20:25
  • 2
    \$\begingroup\$ The real power of Iocaine Powder was that is switches between using the markov and beating-markov. It starts out as smart markov, but once it senses (starts losing) it jumps into beating-markov mode. Should be easy to add. \$\endgroup\$ Commented Jul 30, 2014 at 14:39
  • \$\begingroup\$ Ahh, clever! Not gonna lie, I had only heard Iocaine described to me, not actually looked at it in detail. Feel free to modify my code if you'd like or submit your own and get the credit! \$\endgroup\$ Commented Jul 30, 2014 at 17:11
8
\$\begingroup\$

Boring Bot (Java)

He assumes everyone always plays the same thing and plans accordingly. He usually picks rocks in a ties though ’cause so does everyone else right?

public class BoringBot
{
    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("P");
            return;
        }

        char[] oppPreviousPlays = args[1].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++;
            }
        }

        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\$
8
  • \$\begingroup\$ Note tht if this is a strategy that someone else is already using let me know and I'll delete. It just feels like the obvious one I didn't already see. \$\endgroup\$
    – kaine
    Commented Jul 25, 2014 at 18:15
  • \$\begingroup\$ is this C#. you're .length properties are wrong. and theres no method max \$\endgroup\$ Commented Jul 25, 2014 at 18:46
  • \$\begingroup\$ @EoinCampbell It is java, i've been playing with both and apparently forgot which commands belong to which. \$\endgroup\$
    – kaine
    Commented Jul 25, 2014 at 19:09
  • \$\begingroup\$ ah cool. leave it with me and I'll get it included. \$\endgroup\$ Commented Jul 25, 2014 at 19:12
  • \$\begingroup\$ still broken. running jre8 - java BoringBot.java - Error: Could not find or load main class D:\My Software Dev\big-bang-game\BigBang.Orchestrator\bin\Debug\Players\BoringBot\BoringBot.java - \$\endgroup\$ Commented Jul 26, 2014 at 0:24
8
\$\begingroup\$

HuddleWolfTheConqueror - C#

HuddleWolf is back and better than ever. He will beat Sheldor the Conqueror at his own silly game. HuddleWolf is smart enough to identify and counter spammerbots. For more intelligent opponents, HuddleWolf uses his knowledge of basic 5th grade statistics and utilizes a weighted dice roll based on the opposition's history of plays.

using System;
using System.Collections.Generic;
using System.Linq;

public class HuddleWolfTheConqueror
{

    public static readonly char[] s = new[] { 'R', 'P', 'S', 'L', 'V' };

    public static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine(pickRandom());
            return;
        }

        char[] myPlays = args[0].ToCharArray();
        char[] oppPlays = args[1].ToCharArray();

        char tryPredict = canPredictCounter(oppPlays);
        if (tryPredict != '^')
        {
            Console.WriteLine(tryPredict);
        }
        else
        {
            Console.WriteLine(pickRandom());
        }
        return;
    }


    public static char canPredictCounter(char[] history)
    {
        // don't predict if insufficient data
        if (history.Length < 5)
        {
            return '^';
        }

        // calculate probability of win for each choice
        Dictionary<char, double> dic = getBestProabability(history);

        // get item with highest probability of win
        List<char> maxVals = new List<char>();
        char maxVal = '^';
        double mostFreq = 0;
        foreach (var kvp in dic)
        {
            if (kvp.Value > mostFreq)
            {
                mostFreq = kvp.Value;
            }
        }
        foreach (var kvp in dic)
        {
            if (kvp.Value == mostFreq)
            {
                maxVals.Add(kvp.Key);
            }
        }

        // return error
        if (maxVals.Count == 0)
        {
            return maxVal;
        }

        // if distribution is not uniform, play best play
        if (maxVals.Count <= 3)
        {
            Random r = new Random(Environment.TickCount);
            return maxVals[r.Next(0, maxVals.Count)];
        }

        // if probability is close to uniform, use weighted dice roll
        if (maxVals.Count == 4)
        {
            return weightedRandom(dic);
        }

        // if probability is uniform, use random dice roll
        if (maxVals.Count >= 5)
        {
            return pickRandom();
        }

        // return error
        return '^';
    }

    public static Dictionary<char, double> getBestProabability(char[] history)
    {
        Dictionary<char, double> dic = new Dictionary<char, double>();
        foreach (char c in s)
        {
            dic.Add(c, 0);
        }
        foreach (char c in history)
        {
            if (dic.ContainsKey(c))
            {
                switch(c)
                {
                    case 'R' : 
                        dic['P'] += (1.0/(double)history.Length);
                        dic['V'] += (1.0/(double)history.Length);
                        break;
                    case 'P' : 
                        dic['S'] += (1.0/(double)history.Length);
                        dic['L'] += (1.0/(double)history.Length);
                        break;
                    case 'S' : 
                        dic['V'] += (1.0/(double)history.Length);
                        dic['R'] += (1.0/(double)history.Length);
                        break;
                    case 'L' : 
                        dic['R'] += (1.0/(double)history.Length);
                        dic['S'] += (1.0/(double)history.Length);
                        break;
                    case 'V' : 
                        dic['L'] += (1.0/(double)history.Length);
                        dic['P'] += (1.0/(double)history.Length);
                        break;
                    default : 
                        break;

                }
            }
        }
        return dic;
    }

    public static char weightedRandom(Dictionary<char, double> dic)
    {
        Random r = new Random(Environment.TickCount);
        int next = r.Next(0, 100);
        int curVal = 0;
        foreach (var kvp in dic)
        {
            curVal += (int)(kvp.Value*100);
            if (curVal > next)
            {
                return kvp.Key;
            }
        }
        return '^';
    }

    public static char pickRandom()
    {
        Random r = new Random(Environment.TickCount);
        int next = r.Next(0, 5);
        return s[next];
    }
}
\$\endgroup\$
8
\$\begingroup\$

ToddlerProof

This fairly stupid bot assumes it is playing a toddler who will "chase" its moves, always trying to beat whatever was last thrown. If the bot is beaten several times in a row, it jumps to a new point in the pattern. It is based on my strategy for always beating my much younger brother. :)

EDIT:: Changed the length of a loss streak required to jump into random throws. Also fixed a major bug with the random jump.

Save as ToddlerProof.java, compile, then run with java ToddlerProof [me] [them]

import java.util.HashMap;
public class ToddlerProof
{
    char[] moves = new char[]{'R', 'P', 'S', 'L', 'V'};
    public static void main(String[] args)
    {
        if(args.length<1) //first Round
        {
            System.out.print('V');//Spock is best
            return;
        }
        else
        {
            String them = args[1];
            String me = args[0];
            int streak = 0;

            HashMap<Character, Character> nextMove = new HashMap<Character, Character>();
            //Next move beats things that beat my last move
            nextMove.put('L', 'V');
            nextMove.put('V', 'S');
            nextMove.put('S', 'P');
            nextMove.put('P', 'R');
            nextMove.put('R', 'L');
            //Check if last round was a tie or the opponent beat me
            int lastResult = winner(me.charAt(me.length()-1), them.charAt(them.length()-1));
            if(lastResult == 0)
            {
                //tie, so they will chase my last throw
                System.out.print(nextMove.get(me.charAt(me.length()-1)));

                return;
            }
            else if(lastResult == 1)
            {
                //I won, so they will chase my last throw
                System.out.print(nextMove.get(me.charAt(me.length()-1)));


                return;
            }

            else{
                //I lost
                //find streak
                for(int i = 0; i<me.length(); i++)
                {
                    int a = winner(me.charAt(i), them.charAt(i));
                    if(a >= 0) streak = 0;
                    else streak++;
                }
                //check lossStreak
                //If the streak is 2, then a rotation will make it even.
                //if it is >2, something bad has happened and I need to adjust.
                if(streak>2)
                {
                    //if they are on to me, do something random-ish
                    int r = (((them.length()+me.length()-1)*13)/7)%4;
                    System.out.print(move[r]);
                    return;
                }
                //otherwise, go on with the plan
                System.out.print(nextMove.get(me.charAt(me.length()-1)));
                return;
            }
        }
    }
    public static int winner(char me, char them)
    {
        //check for tie
        if(me == them) return 0;
        //check if they won
        if(me=='V' && (them == 'L' || them == 'P')) return -1;
        if(me=='S' && (them == 'V' || them == 'R')) return -1;
        if(me=='P' && (them == 'S' || them == 'L')) return -1;
        if(me=='R' && (them == 'P' || them == 'V')) return -1;
        if(me=='L' && (them == 'R' || them == 'S')) return -1;
        //otherwise, I won
        return 1;
    }
}
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Should we be using print or println?... I wasn't certain. \$\endgroup\$
    – kaine
    Commented Jul 25, 2014 at 20:31
  • \$\begingroup\$ Hmmm. I would imagine both would work, but I could see println messing up if the control program grabbed the newline instead of the character. Thanks for pointing that out, I'll edit my code just in case \$\endgroup\$
    – Stranjyr
    Commented Jul 25, 2014 at 20:34
  • \$\begingroup\$ @Stranjyr there were some bugs in your last run. It didn't bomb the control program but if you search the history for "ToddlerProof plays n" it looks like your bot was returning null for certain hands and then autolosing the hand. Example game is "Echo & ToddlerProof" where Echo played "LVSPRLV" before your bot started to crap out. \$\endgroup\$ Commented Jul 30, 2014 at 13:09
  • \$\begingroup\$ @Eion Campbell Thanks for mentioning it. I saw that earlier when you posted the logs from the failed tourney, and I think I have it fixed. It was running into an error where if it lost more than 5 straight, instead of jumping to a random play it just threw an invalid value. And then, because that made it lose, it threw another invalid value. A vicious cycle. \$\endgroup\$
    – Stranjyr
    Commented Jul 30, 2014 at 13:32
  • \$\begingroup\$ Cool. Have it updated in the control prog now. \$\endgroup\$ Commented Jul 31, 2014 at 17:09
8
\$\begingroup\$

Bart Simpson

"Good old rock! Nothing beats rock!"

puts 'R'

Run as

ruby DoTheBartman.rb

Lisa Simpson

"Poor, predictable Bart. Always chooses rock."

puts 'P'

Run as

ruby LisaSimpson.rb

Better Lisa Simpson

I felt bad about making Lisa quite so stupid, so I allowed her to randomly choose between either of the hands that will beat rock. Still stupid, but she is a Simpson after all. Maybe a crayon got stuck in her brain?

puts ['P','V'].sample

Run as

ruby BetterLisaSimpson.rb
\$\endgroup\$
5
  • 2
    \$\begingroup\$ Minor name clash. +1 anyway. \$\endgroup\$ Commented Jul 28, 2014 at 14:06
  • \$\begingroup\$ @MartinBüttner Damn, didn't notice that. The programs still seem to do different things though - and at least Lisa here can feel more superior by beating two different versions of her brother. \$\endgroup\$ Commented Jul 28, 2014 at 14:17
  • 1
    \$\begingroup\$ Sheldor agrees... there shall be a BartBot and a BartSimpson :) \$\endgroup\$ Commented Jul 28, 2014 at 15:36
  • 3
    \$\begingroup\$ We only have BortBot. \$\endgroup\$
    – JoshDM
    Commented Jul 28, 2014 at 18:42
  • 1
    \$\begingroup\$ These will get slaughtered by markov :) \$\endgroup\$
    – Cruncher
    Commented Jul 29, 2014 at 19:03
7
\$\begingroup\$

Echo

Written in C#. Compile with csc Echo.cs. Run like Echo.exe ARG1 ARG2.

The first run, Echo takes a random option. Every run after the first, Echo simply repeats the opponent's latest action.

using System;

namespace Echo
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Random r = new Random();
                string[] options = new string[] { "R", "P", "S", "L", "V" };
                Console.WriteLine(options[r.Next(0, options.Length)]);
            }
            else if (args.Length == 2)
            {
                string opponentHistory = args[1];
                Console.WriteLine(opponentHistory[opponentHistory.Length - 1]);
            }
        }
    }
}
\$\endgroup\$
7
\$\begingroup\$

Vulcan, Ruby

My fingers are glued together.

puts 'V'

Run like

ruby vulcan.rb

(I think this is the only in-character strategy for your background setting.)

\$\endgroup\$
3
  • \$\begingroup\$ Need to look back at the episodes to see if anyone was born with a forked tongue. LizardMan FTW !!! \$\endgroup\$ Commented Jul 25, 2014 at 17:05
  • 3
    \$\begingroup\$ But isn't this how everyone on big bang plays anyways? \$\endgroup\$
    – kaine
    Commented Jul 25, 2014 at 17:24
  • 2
    \$\begingroup\$ @anotherguest That's what I meant by "this is the only in-character strategy". \$\endgroup\$ Commented Jul 25, 2014 at 17:24
6
\$\begingroup\$

Tyrannosaurus, Godzilla, Barney... Lizards Rule. Occasionally they get in trouble and need to call Spock or throw Rocks

using System;
public class LizardsRule
{
    public static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine("L");
            return;
        }
        char[] oppPreviousPlays = args[1].ToCharArray();
        var oppLen = oppPreviousPlays.Length;
        if (oppPreviousPlays.Length > 2
            && oppPreviousPlays[oppLen - 1] == 'R'
            && oppPreviousPlays[oppLen - 2] == 'R'
            && oppPreviousPlays[oppLen - 3] == 'R')
        {
            //It's an avalance, someone call Spock
            Console.WriteLine("V");
            return;
        }

        if (oppPreviousPlays.Length > 2
                && oppPreviousPlays[oppLen - 1] == 'S'
                && oppPreviousPlays[oppLen - 2] == 'S'
                && oppPreviousPlays[oppLen - 3] == 'S')
        {
            //Scissors, Drop your tail and pick up a rock
            Console.WriteLine("R");
            return;
        }

        //Unleash the Fury Godzilla
        Console.WriteLine("L");     
    }
}
\$\endgroup\$
6
\$\begingroup\$

BayesianBot, Perl (now v2!)

Above everything else, this is a unique program. In it, you shall see the brilliant fusion of statistics and horrible programming form. Also, this bot probably breaks many rules of Bayesian statistics, but the name sounds cooler.

The core essence of this bot is its creation of 250 different predictive models. Each model takes the form of "Given that I played rock last turn and my opponent played scissors two turns ago, this is the probability distribution for my opponent's next move." Each probability distribution takes the form of a multi-dimensional Dirichlet distribution.

Each turn, the predictions of all applicable models (typically 10) are multiplied together to form an overall prediction, which is then used to determine which moves have the highest expected payoff.

Edit 1: In this version, I changed the prior distribution and made the bot more randomized when it is losing.

There are a few things which may be subject to improvement, such as the number of models (250 is only a 3 digit number), the choice of prior distribution (currently Dir(3,3,3,3,3)), and the method of fusing predictions. Also, I never bothered to normalize any of the probability distributions, which is okay for now because I'm multiplying them.

I don't have super high expectations, but I hope this bot will be able to do well.

my ($phist, $ohist) = @ARGV;

my %text2num = ('R',0,'V',1,'P',2,'L',3,'S',4);  #the RVPLS ordering is superior
my @num2text = ('R','V','P','L','S');

@phist = map($text2num{$_},split(//,$phist));
@ohist = map($text2num{$_},split(//,$ohist));

$lowerlimit = 0;
for($lowerlimit..~~@phist-3){$curloc=$_;
 $result = $ohist[$curloc+2];
 @moveset = ($ohist[$curloc],$ohist[$curloc+1],$phist[$curloc],$phist[$curloc+1]);
 for(0..3){$a=$_;
  for(0..$a){$b=$_;
   $predict[$a][$b][$moveset[$a]][$moveset[$b]][$result]++;
  }
 }
}

@recentmoves = ($ohist[-2],$ohist[-1],$phist[-2],$phist[-1]);

@curpred = (1,1,1,1,1);

for(0..3){$a=$_;
 for(0..$a){$b=$_;
  for(0..4){$move=$_;
   $curpred[$move] *= $predict[$a][$b][$recentmoves[$a]][$recentmoves[$b]][$move]/3+1;
  }
 }
}

@bestmove = (0,0,0,0,0);
for(0..4){
 $bestmove[$_] = $curpred[$_]/2+$curpred[$_-1]+$curpred[$_-2];
}

$max = 0;
for(0..4){
 if($bestmove[$_]>$max){
  $max = $bestmove[$_];
 }
}
@options=();
$offset=0;
if(($ohist[-1] - $phist[-1])%5 < 2 && ($ohist[-2] - $phist[-2])%5 < 2 && ($ohist[-3] - $phist[-3])%5 < 2){  #frequentist alert!
 $offset=int(rand(3));
}
for(0..4){
 if($bestmove[$_] == $max){
  push(@options,$num2text[($_+$offset)%5]);
 }
}
$outputb = $options[int(rand(~~@options))];

print "$outputb";

I've been running this program like so:

perl BayesianBot.plx
\$\endgroup\$
0
5
\$\begingroup\$

DynamicBot

Dynamic bot is almost always changing. It really hates repeating itself

import sys, random
choices = ['L','V','S','P','R'] * 20
if len(sys.argv) > 1:
    my_history = sys.argv[1]
    [choices.remove(my_history[-1]) for i in range(15)]
print(choices[random.randrange(len(choices))])

Language: Python 3.4.1

Command: python dynamicbot.py <history> or python3 dynamicbot.py <history> depending on your system

\$\endgroup\$
1
  • \$\begingroup\$ Yeah, thought about that. \$\endgroup\$
    – seequ
    Commented Jul 28, 2014 at 8:15
5
\$\begingroup\$

SmartBot - Java

My first ever entry for anything on this site!

Though not a very creative name...

SmartBot finds sequences of moves where the opponent and/or itself's moves are similar to the moves last made and plans accordingly.

name = SmartBot

I think to run it, correct me if I am wrong.

java -jar SmartBot.jar

import java.util.ArrayList;
public class SmartBot {
    public static void main(String[] args) {
        if(args.length ==0){
            System.out.print("L");
            return;
        }
        if(args[0].length()<3){
            String[] randLetter = new String[]{"R","P","S","L","V"};
            System.out.print(randLetter[(int) Math.floor(Math.random()*5)]);
            return;
        }
        String myHistory = args[0];
        String otherHistory = args[1];

        double rScore,pScore,sScore,lScore,vScore;//score - highest = highest probability of next opponent move
        rScore = pScore = sScore = lScore = vScore = 0;
        lScore = .001;
        ArrayList<ArrayList<Integer>> moveHits = new ArrayList<ArrayList<Integer>>();
        for(int g = 0;g<2;g++){
            for(int i=1;i<(myHistory.length() / 2) + 1;i++){
                if(g==0){
                    moveHits.add(findAll(myHistory.substring(myHistory.length() - i),myHistory));
                }
                else{
                    moveHits.add(findAll(otherHistory.substring(otherHistory.length() - i),otherHistory));
                }
            }
            for(int i = 0; i < moveHits.size();i++){
                int matchingMoves = i+1;
                ArrayList<Integer> moveIndexes = moveHits.get(i);
                for(Integer index:moveIndexes){
                    if(index+matchingMoves +1<= otherHistory.length()){
                        char nextMove = otherHistory.charAt(index + matchingMoves-1);
                        if(nextMove=='R'){rScore = rScore + matchingMoves;}
                        if(nextMove=='P'){pScore = pScore + matchingMoves;}
                        if(nextMove=='S'){sScore = sScore + matchingMoves;}
                        if(nextMove=='L'){lScore = lScore + matchingMoves;}
                        if(nextMove=='V'){vScore = vScore + matchingMoves;}
                    }
                }
            }
        }
        if(rScore >= pScore && rScore >= sScore && rScore >= lScore && rScore >= vScore){
            System.out.print("V");
            return;
        }
        if(pScore >= rScore && pScore >= sScore && pScore >= lScore && pScore >= vScore){
            System.out.print("L");
            return;
        }
        if(sScore >= pScore && sScore >= rScore && sScore >= lScore && sScore >= vScore){
            System.out.print("R");
            return;
        }
        if(vScore >= pScore && vScore >= sScore && vScore >= lScore && vScore >= rScore){
            System.out.print("L");
            return;
        }
        if(lScore >= pScore && lScore >= sScore && lScore >= rScore && lScore >= vScore){
            System.out.print("S");
        }
        return;
    }
    public static ArrayList<Integer> findAll(String substring,String realString){
        ArrayList<Integer> ocurrences = new ArrayList<Integer>();
        Integer index = realString.indexOf(substring);
        if(index==-1){return ocurrences;}
        ocurrences.add(index+1);
        while(index!=-1){
            index = realString.indexOf(substring,index + 1);
            if(index!=-1){
                ocurrences.add(index+1);
            }
        }
        return ocurrences;
    }
}

It assigns a score for each possible next move by the number of times similar patterns have happened.

It slightly favors lizard.

\$\endgroup\$
2
  • \$\begingroup\$ I believe that's how you run it if you jar it first. If you simply compile it first, then java ABot should work (remember to name the file the same as the public class) \$\endgroup\$
    – Justin
    Commented Jul 26, 2014 at 4:49
  • \$\begingroup\$ Thanks! As a relatively new programmer I was not aware of this. \$\endgroup\$ Commented Jul 26, 2014 at 13:39
5
\$\begingroup\$

SpockOrRock - PHP

SpockOrRock

When played in the real world, most people instinctively pick scissors. This bot picks either Spock or Rock to beat the average player. It's not bothered about previous rounds.

run with php spockorrock.php

<?php

//Pick either Spock or Rock
if (rand(0,1) == 0)     echo("R\n");
else                    echo("V\n");


?>
\$\endgroup\$
4
\$\begingroup\$

SlowLizard, Ruby

After starting with Lizard, it always picks a random move which beats the opponent's previous move.

responses = {
  'R' => ['P', 'V'],
  'P' => ['S', 'L'],
  'S' => ['R', 'V'],
  'L' => ['S', 'R'],
  'V' => ['P', 'L']
}

if ARGV.length == 0
  puts 'L'
else
  puts responses[ARGV[1][-1]].sample
end

Run like

ruby slowlizard.rb
\$\endgroup\$
4
\$\begingroup\$

LexicographicBot

This bot likes to order his letters, so he will choose a response that is 1 higher than his opponent gave in the previous round--unless the opponent chose Vulcan, then he randomly picks a response.

import sys
import random

choices = ["L", "P", "R", "S", "V"]

total = len(sys.argv)
if total==1:
    print("L")
    sys.exit()
    
opponent = sys.argv[2]
opponent_last = opponent[-1]

if opponent_last == choices[-1]:
    print(random.choice(choices))
else:
    next = choices.index(opponent_last)+1
    print(choices[next])

This expects the opponent hand to be dealt second:

                           me
                            v
python LexicographicBot.py SR RV
                              ^
                            opponent
\$\endgroup\$
3
  • \$\begingroup\$ @MartinBüttner: Command added! I have been pretty busy at work trying to get something published, hence the disappearance. \$\endgroup\$
    – Kyle Kanos
    Commented Jul 25, 2014 at 16:31
  • \$\begingroup\$ breaks on first run with no args. Traceback (most recent call last): File "LexicographicBot\LexicographicBot.py", line 10, in <module> opponent = sys.argv[2] IndexError: list index out of range \$\endgroup\$ Commented Jul 25, 2014 at 19:30
  • \$\begingroup\$ @EoinCampbell: I forgot the exit clause on first run, it's been added & should work fine now. \$\endgroup\$
    – Kyle Kanos
    Commented Jul 25, 2014 at 19:55
4
\$\begingroup\$

Werevulcan - Ruby

Run as ruby werevulcan.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

  # For the first 30 rounds, pick a random move that isn't Spock
  if player.to_s.size < 30
    %w[L P R S].sample
  elsif opponent.chars.to_a.uniq.size < 5
    exploit(opponent)
  else
    # Pick a random move that's biased toward Spock and against lizards
    %w[L P P R R S S V V V].sample
  end

end

def exploit(opponent)
  @moves.shuffle.max_by{ |m| opponent.chars.map{|o| score(m,o) }.reduce(:+) }
end

puts move

The werevulcan looks normal by day, but when the moon rises, its ears grow pointy, and its moves grow more logical.

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

Analogizer - Ruby

Run with ruby analogizer.rb. I've made a logic fix to the code, but no idea why there were errors with this.

@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
  # Throw six lizards in the beginning to confuse opponent
  when 0..5
    'L'
  when 6
    'V'
  when 7
    'S'
  when 8
    'P'
  when 9
    'R'
  else
    analyze_history(player.chars.to_a, opponent.chars.to_a)
  end

end

def analyze_history(player, opponent)
  my_last_move = player.last
  predicted_moves = Hash.new {0}
  opponent_reactions = player.zip(opponent.drop(1))

  # Check whether opponent tended to make a move that would've beaten, lost, or tied my last move
  opponent_reactions.each do |my_move, reaction|
    score = score(reaction, my_move)
    analogous_moves = @moves.select { |move| score == score(move, my_last_move) }
    analogous_moves.each { |move| predicted_moves[move] += 1 }
  end

  # Assume if an opponent has never made a certain move, it never will
  @moves.each { |m| predicted_moves[m] = 0 unless opponent.include?(m) }

  # Pick the move with the best score against opponent's possible moves, weighted by their likelihood, picking randomly for ties
  @moves.shuffle.max_by{ |m| predicted_moves.map { |predicted, freq| score(m, predicted) * freq }.reduce(0,:+) }

end

puts move

Assumes the opposing bot is always reacting to my previous move, and either picking something that would beat it, something that would lose to it, or the same move, possibly from a restricted set of possible moves. It then picks the best move given that assumption.

Except that the first ten moves are hardcoded: first I pretend I only know lizard, then I assume my opponent always throws something to beat the last thing I threw until I have enough data for proper analysis.

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

Java - SelfLoathingBot

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

Bot starts randomly, then ~33% to go random, or ~33% to play a winning tactic against either of the immediately prior plays, with 50% choice of winning tactic.

import java.util.Random;

public class SelfLoathingBot {

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

        int choice = args.length == 0 ? 0 : RANDOM.nextInt(3);

        char play;

        switch (choice) {

            case 1 :

                // 33.3% chance Play myself
                play = antiPlay(args[0]);
                break;

            case 2 :

                // 33.3% chance Play opponent just in case opponent is screwy like that
                play = antiPlay(args[1]);
                break;

            default :

                // 33.3% chance 100% Random
                play = randomPlay();
        }

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

The Analyst

The analyst analyzes some stuff and does some things to try to beat you.

compile with javac Analyst.java and run as java Analyst

import java.util.Random;

public class Analyst{
    public static void main(String[] args){
        char action = 'S';
        
        try{
            char[] enemyMoves = null, myMoves = null;
    
            //first move is random
            if(args.length == 0){
                System.out.print(randomMove());
                System.exit(0);
            //moves 2-3 will beat their last move
            }else if(args[0].length() < 8){
                System.out.print(counterFor(args[1].charAt(args[1].length()-1)));
                System.exit(0);
            //following moves will execute some analyzation stuff
            }else{
                //get previous moves
                myMoves = args[0].toCharArray();
                enemyMoves = args[1].toCharArray();
            }
    
            //test if they're trying to beat our last move
            if(beats(enemyMoves[enemyMoves.length-1], myMoves[myMoves.length-2])){
                action = counterFor(counterFor(myMoves[myMoves.length-1]));
            }
            //test if they're copying our last move
            else if(enemyMoves[enemyMoves.length-1] == myMoves[myMoves.length-2]){
                action = counterFor(myMoves[myMoves.length-1]);
            }
            //else beat whatever they've done the most of
            else{
                action = counterFor(countMost(enemyMoves));
            }
    
            //if they've beaten us for the first 40 moves, do the opposite of what ive been doing
            if(theyreSmarter(myMoves, enemyMoves)){
                action = counterFor(action);
            }
        
        //if you break my program do something random
        }catch (Exception e){
            action = randomMove();
        }

        System.out.print(action);
    }

    private static char randomMove(){
        Random rand = new Random(System.currentTimeMillis());
        int randomMove = rand.nextInt(5);
        
        switch (randomMove){
            case 0: return 'R';
            case 1: return 'P';
            case 2: return 'S';
            case 3: return 'L';
            default: return 'V';
        }
    }

    private static char counterFor(char move){
        Random rand = new Random(System.currentTimeMillis());
        int moveSet = rand.nextInt(2);

        if(moveSet == 0){
            switch (move){
                case 'R': return 'P'; 
                case 'P': return 'S'; 
                case 'S': return 'R'; 
                case 'L': return 'R'; 
                default: return 'P';
            }
        }else{
            switch (move){
                case 'R': return 'V'; 
                case 'P': return 'L'; 
                case 'S': return 'V'; 
                case 'L': return 'S'; 
                default: return 'L';
            }
        }
    }

    private static boolean beats(char move1, char move2){
        if(move1 == 'R'){
            if((move2 == 'S') || (move2 == 'L')){
                return true;
            }else{
                return false;
            }
        }else if(move1 == 'P'){
            if((move2 == 'R') || (move2 == 'V')){
                return true;
            }else{
                return false;
            }
        }else if(move1 == 'S'){
            if((move2 == 'L') || (move2 == 'P')){
                return true;
            }else{
                return false;
            }
        }else if(move1 == 'L'){
            if((move2 == 'P') || (move2 == 'V')){
                return true;
            }else{
                return false;
            }
        }else{
            if((move2 == 'R') || (move2 == 'S')){
                return true;
            }else{
                return false;
            }
        }
    }

    private static char countMost(char[] moves){
        int[] enemyMoveList = {0,0,0,0,0};

        for(int i=0; i<moves.length; i++){
            if(moves[i] == 'R'){
                enemyMoveList[0]++;
            }else if(moves[i] == 'P'){
                enemyMoveList[1]++;
            }else if(moves[i] == 'S'){
                enemyMoveList[2]++;
            }else if(moves[i] == 'L'){
                enemyMoveList[3]++;
            }else if(moves[i] == 'V'){
                enemyMoveList[4]++;
            }
        }

        int max = 0, maxIndex = 0;
        for(int i=0; i<5; i++){
            if(enemyMoveList[i] > max){
                max = enemyMoveList[i];
                maxIndex = i;
            }
        }

        switch (maxIndex){
            case 0: return 'R';
            case 1: return 'P';
            case 2: return 'S';
            case 3: return 'L';
            default: return 'V';
        }
    }

    private static boolean theyreSmarter(char[] myMoves, char[] enemyMoves){
        int loseCounter = 0;

        if(enemyMoves.length >= 40){
            for(int i=0; i<40; i++){
                if(beats(enemyMoves[i],myMoves[i])){
                    loseCounter++;
                }
            }
        }else{
            return false;
        }

        if(loseCounter > 20){
            return true;
        }else{
            return false;
        }
    }
}
\$\endgroup\$
4
\$\begingroup\$

The Gambler - Python 2

import sys
import random

MODE = 1

moves = 'RSLPV'

def element_sums(a, b):
    return [a[i] + b[i] for i in xrange(len(a))]

def move_scores(p):
    def calc(to_beat):
        return ['LDW'.find('DLLWW'[moves.find(m)-moves.find(to_beat)]) for m in moves]

    return dict(zip(moves, element_sums(calc(p[0]), calc(p[1]))))

def move_chooser(my_history, opponent_history):
    predict = sorted(moves, key=opponent_history.count, reverse=MODE)[-2:]
    scores = move_scores(predict)
    return max(scores, key=lambda k:scores[k])

if __name__ == '__main__':
    if len(sys.argv) == 3:
        print move_chooser(*sys.argv[1:])
    elif len(sys.argv) == 1:
        print random.choice(moves)

Contrary to the name, the only time randomness is used in this program is on the first round, when there's no information. Instead, it's named for the gambler's fallacy, the belief that if a random event has happened less often in the past, it's more likely to happen in the future. For example, if you flip a fair coin 20 times, and the first 15 are heads, the gambler's fallacy states that the odds of the remaining flips being tails are increased. Of course, this is untrue; regardless of the previous flips, a fair coin's odds of coming up tails is always 50%.

This program analyzes the opponent's history, finds the 2 moves that it has used the least so far, and assumes that the opponent's move this time will be one of those two. Assigning 2 to a win, 1 to a draw and 0 to a loss, it finds the move with the maximum score against these two predicted moves and throws that.

The Gambler's Brother - Python 2

import sys
import random

MODE = 0

moves = 'RSLPV'

def element_sums(a, b):
    return [a[i] + b[i] for i in xrange(len(a))]

def move_scores(p):
    def calc(to_beat):
        return ['LDW'.find('DLLWW'[moves.find(m)-moves.find(to_beat)]) for m in moves]

    return dict(zip(moves, element_sums(calc(p[0]), calc(p[1]))))

def move_chooser(my_history, opponent_history):
    predict = sorted(moves, key=opponent_history.count, reverse=MODE)[-2:]
    scores = move_scores(predict)
    return max(scores, key=lambda k:scores[k])

if __name__ == '__main__':
    if len(sys.argv) == 3:
        print move_chooser(*sys.argv[1:])
    elif len(sys.argv) == 1:
        print random.choice(moves)

By switching the MODE variable to 0, this program will operate based on a related fallacy, also sometimes referred to as the gambler's fallacy. It states that if a random event has been happened more often in the past, it's more likely to happen in the future. For example, if you flip a coin 20 times and the first 15 are heads, this fallacy states that the remaining flips are more likely to be heads, since there's currently a streak. In mode 0, this program operates the same way, except that it assumes the opponent will throw one of the two moves it's thrown most often so far.

So yes, these two programs are only one character apart. :)

\$\endgroup\$
3
  • \$\begingroup\$ On what condition does TheGambler change MODE? \$\endgroup\$ Commented Jul 29, 2014 at 14:50
  • \$\begingroup\$ @DrRDizzle It doesn't, it looks like this is a submit of two bots in one. \$\endgroup\$ Commented Jul 29, 2014 at 16:17
  • 2
    \$\begingroup\$ Would this program not be more effective if MODE switched if you lose more than a certain amount of times in a row? \$\endgroup\$ Commented Jul 31, 2014 at 9:00
4
\$\begingroup\$

Dienstag (Python 2)

My first entry Pony seems to fare quite well with all its second guessing (tripple guessing, ...) and meta reasoning. But is that even necessary?

So, here is Dienstag, Pony's little friend, with only one of the 55 strategies: Predict the opponent's next move and beat it.

On the long run Dienstag wins or ties with every Bot in the top ten of the current leaderboard. Except for Pony that is.

import sys
if len(sys.argv)<2 or len(sys.argv[1])<2: print 'L'; sys.exit()
hist = [map('RPSVL'.index, p) for p in zip(sys.argv[1], sys.argv[2])]
N = len(hist)
cand = range(N-1)
for l in xrange(1,N):
    cand = ([c for c in cand if c>=l and hist[c-l+1]==hist[-l]] or cand[-1:])
print 'RPSVL'[(hist[cand[-1]+1][1]+(1,3)[N%2==0])%5]

Run as:

python Dienstag.py

I admit that the code is a bit obfuscated. Should anyone care to know more about it, I might add explanations.

Edit: Here is a short example walkthrough to explain the idea:

  • The program gets the history of it's own and the opponent's moves:

    sys.arg[1] = 'LLVLLVL', sys.arg[2] = 'RPSPSSP'

  • The history is combined into a list of pairs and the moves are translated to numbers (R=0, ...):

    hist = [[4, 0], [4, 1], [3, 2], [4, 1], [4, 2], [3, 2], [4, 1]]

  • The number of rounds played so far is determined:

    N = 7

  • The basic idea now is to look for the longest unbroken chain of exactly the last moves in the earlier history. The program keeps track of where such a chain ends in the list cand (for 'candidates'). In the beginning, without checking, every position in the history except for the last one is considered:

    cand = [0, 1, 2, 3, 4, 5]

  • Now the length of possible chains is increased step by step. For chain length l = 1 it looks for previous occurrences of the last move pair [4, 1]. This can be found at history position 1 and 3. Only these are kept in the cand list:

    cand = [1, 3]

  • Next, for l = 2 it checks which of the possible candidates was preceded by the second to last move pair [3, 2]. This is only the case for position 3:

    cand = [3]

  • For l = 3 and more there are no previous chains of that length and cand would be empty. In this case the last element of cand is kept:

    cand = [3]

  • The bot now assumes that history will repeat. The last time the cain [3, 2], [4, 1] occurred, it was followed by [4, 2]. So, the opponent played 2 (scissors) which can be beaten by (2+1)%5 = 3 (Spock) or (2+3)%5 = 0 (rock). The bot answers, with the first or second alternative depending on whether N is odd or even just to introduce some variance.

  • Here move 3 is chosen which is then translated back:

    print 'V'

Note: Dienstag has time complexity O(N2) for returning the next move after N rounds. Pony has time complexity O(N3). So in this aspect they are probably much worse then most other entries.

\$\endgroup\$
3
  • \$\begingroup\$ please do. this is an awesome learning experience for me. I usually live in C#/Java land so all the lua,ruby,python,haskell madness is very interesting to me. \$\endgroup\$ Commented Aug 1, 2014 at 10:07
  • \$\begingroup\$ I'm also tempted to add an extra instance of Pony to the games. It'll be like having to fight your mirror self in the 3rd to last Mortal Combat levels ;-) \$\endgroup\$ Commented Aug 1, 2014 at 10:27
  • \$\begingroup\$ @EoinCampbell :-) At least the direct match Pony vs. Pony would be a perfect draw. There is no element of randomness in both my bots. \$\endgroup\$
    – Emil
    Commented Aug 1, 2014 at 11:30
3
\$\begingroup\$

Bash Rocks

Is cygwin too much to ask as a runtime?

bashrocks.sh:

#!/bin/bash
HAND=(R P S L V)
RAND=`od -A n -t d -N 1 /dev/urandom | xargs`
echo ${HAND[ $RAND  % 5 ]}

and run it like so:

sh bashrocks.sh
\$\endgroup\$
8
  • 5
    \$\begingroup\$ After reading the title, I'm slightly disappointed that you do anything but R. ;) \$\endgroup\$ Commented Jul 25, 2014 at 16:53
  • \$\begingroup\$ @mccannf. having some problems with this one... I've installed cygwin and modified your scripts with fully qualified paths to C:\Cygwin\bin for od.exe, xargs.exe & echo.exe. still getting the following error. C:/Cygwin/bin/xargs: echo: No such file or directory % 5 ")syntax error: operand expected (error token is " \$\endgroup\$ Commented Jul 27, 2014 at 20:44
  • \$\begingroup\$ @EoinCampbell - when you create the file in windows, can you then run dos2unix on the file in cygwin before executing it? \$\endgroup\$
    – mccannf
    Commented Jul 27, 2014 at 21:00
  • \$\begingroup\$ sure. I'll give that a try. \$\endgroup\$ Commented Jul 27, 2014 at 21:13
  • \$\begingroup\$ I think the problem might be with the /dev/urandom statement \$\endgroup\$ Commented Jul 27, 2014 at 21:20
3
\$\begingroup\$

Algorithm

An algorithm for the sake of having one.

Cuz' it always feels safer doing something, the more complicated the better.

Haven't done some serious Math yet, so this algorithm may not be that effective.

import random, sys

if __name__ == '__main__':

    # Graph in adjacency matrix here
    graph = {"S":"PL", "P":"VR", "R":"LS", "L":"VP", "V":"SR"}
    try:
        myHistory = sys.argv[1]
        opHistory = sys.argv[2]
        choices = ""

        # Insert some graph stuff here. Newer versions may include advanced Math.
        for v in graph:
            if opHistory[-1] == v:
                for u in graph:
                    if u in graph[v]:
                        choices += graph[u]

        print random.choice(choices + opHistory[-1])

    except:
        print random.choice("RPSLV")

Python 2 program: python algorithm.py

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Summary of this algorithm: look at what the opponent last played, and then randomly play one of the two moves that would lose against the opponent's last move if they played it again. So it's better against bots that don't play the same move twice in a row. \$\endgroup\$ Commented Jul 26, 2014 at 4:23
  • \$\begingroup\$ Haha. I don't really know if I did made it that way. If I am not wrong, it is actually just a convoluted way of randomly selecting any of the 5 moves. ;) \$\endgroup\$
    – Vectorized
    Commented Jul 26, 2014 at 6:35
3
\$\begingroup\$

FairBot, Ruby

Let's start simple.

puts ['R','P','S','L','V'].sample

Run like

ruby fairbot.rb
\$\endgroup\$
3
  • \$\begingroup\$ small typo on that last 'V' param. have fixed it on myside if you wanna update for completeness \$\endgroup\$ Commented Jul 25, 2014 at 18:50
  • \$\begingroup\$ @EoinCampbell thanks, fixed! \$\endgroup\$ Commented Jul 25, 2014 at 18:56
  • 1
    \$\begingroup\$ The interesting thing is that this has exactly equal odds of winning against ALL strategies. \$\endgroup\$
    – Cruncher
    Commented Jul 29, 2014 at 18:58
3
\$\begingroup\$

ViolentBot

This bot choices the most violent option based on the opponents previous choice:

import sys

choice_dict = {"L" : "S", "P" : "S", "R" : "V", "S" : "V", "V" : "L"}

total = len(sys.argv)
if total==1:
    print("L")
    sys.exit()
    
opponent = sys.argv[2]
opponent_last = opponent[-1]

print(choice_dict[opponent_last])

Run as

python ViolentBot.py (me) (opp)
\$\endgroup\$
3
  • \$\begingroup\$ breaks with no params. Traceback (most recent call last): File "ViolentBot\ViolentBot.py", line 9, in <module> opponent = sys.argv[2] IndexError: list index out of range \$\endgroup\$ Commented Jul 25, 2014 at 19:26
  • \$\begingroup\$ breaks with params. Traceback (most recent call last): File "ViolentBot\ViolentBot.py", line 12, in <module> print(choice_dict[opponent_last]) KeyError: 'S' \$\endgroup\$ Commented Jul 25, 2014 at 19:27
  • \$\begingroup\$ @EoinCampbell: I've added an exit clause for the first run, you should be able to run it now. \$\endgroup\$
    – Kyle Kanos
    Commented Jul 25, 2014 at 19:55
3
\$\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\$
4
  • 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\$ Commented Jul 25, 2014 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
    Commented Jul 25, 2014 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\$ Commented Jul 25, 2014 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\$ Commented Jul 25, 2014 at 23:11

Your Answer

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

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