Python Forum
Forum-wide Competition: Rock Paper Scissors [Meta-thread] - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: General (https://python-forum.io/forum-1.html)
+--- Forum: News and Discussions (https://python-forum.io/forum-31.html)
+--- Thread: Forum-wide Competition: Rock Paper Scissors [Meta-thread] (/thread-388.html)

Pages: 1 2


Forum-wide Competition: Rock Paper Scissors [Meta-thread] - nilamo - Oct-08-2016

This thread exists to hold information about the competition itself... not discussion about individual bots or the current standings (there will be another thread for that).

The goals are to not only provide useful examples of python code, but also to do so in a fun way, while also trying to out-think your opponent.  All source for all bots will be publicly available for anyone to read, so newer bots *should* have a slight advantage over all previous bots, as they can leverage the successes of some while trying to solve shortcomings of others.

Each round, your script will receive (via command line arguments) a space-separated list of all previous rounds, in the format of your-response, other-response.  In this way, there is no reason at all for your script to keep track of any state, as you should just re-calculate whatever you need each time you're run.

If it's the first round, there will be no arguments passed.  For each subsequent round, the results of each previous round will be passed.  For example, if it's the third round, and your first two rounds were 'R' then 'P', while your opponent's first two rounds were 'S' then 'P', the command-line arguments would look like "RS PP", and if you were to look at the command prompt, you'd see "python your_bot.py RS PP".  A bot tester is included at the end of this post, so you can make sure you're receiving input and emitting responses as expected.

TERMINOLOGY:
Bot: Your entree, your program, your script.  There are many bots like it, and now it's time to make your own.
Roshambo: Another name for the game.  Use it if you want other people to look at you funny.
Match: All rounds between two given bots.  Any two bots will only ever have a single match they share.
Round: A single game of RPS.  Many rounds (specifically how many have yet to be decided, but probably an arbitrarily large number, such as 4201) are in a single match. 
Standings: The overall rankings of bots, as determined by which bots have the most match wins.  Tie-breakers... need to be decided later.  Right now, I think it'll be opponent-match-win, with those ties decided by round-win-percent, with THOSE ties decided by opponent-round-win-percent.  Ties after that indicate identical performance, and as such they'll share the spot on the leaderboard.


THE RULES:
1) When your script is called, return (to stdout) either 'R', 'P', or 'S', representing your choice for rock/paper/scissors.
2) Each new bot will face off against all previous bots, and added to overall standings (all bots face each other bot once).
3) Do not use io.  You don't need it for any reason (if you have a reason to disagree, please elaborate).
4) The source for your bot will be available for anyone to see.
5) Each time a new bot is added, all bots will have their matches re-calculated.  This is simply for my benefit, as I don't want to store state anywhere.  I reserve the right to change my mind about this later, since it shouldn't effect your entry at all anyway.  The main effect this rule has, though, is if your bot is in anyway random, it's position could change over time.
6) Do not depend on your file-name, or the order in which you're called.  I might rename your script, you might be the first, you might go second, etc.  (ie: naming your script "zzzzzz.py" does not mean you'll go last).
7) The point is to try to out-think many opponents at once.  If you try to bypass the spirit of the event, for example by reading the matchmaker's memory addresses to find out how your opponent actually responded, your bot will be rejected from the competition.
8) Feel free to submit more than one bot.
9) Bots must be able to run using python 3.x. The future is now!

BENCHMARKS:
I'm submitting two benchmark bots, to demonstrate a) the bare minimum needed for a bot, and b) an example of how to make use of previous rounds to deterministically provide your response.  Both of these bots will be listed in the rankings, but under the moniker "BENCHMARK"... I'm not going to take credit for their (under)performance, and will add my own entry later.

Benchmark Bot #1: Rocky
print('R')
Benchmark Bot #2: CounterPoint
import sys

winning_match = {
    'R': 'P',
    'P': 'S',
    'S': 'R'
}

responses = [other for self, other in sys.argv[1:]]

if responses:
    last = responses[-1]
    print(winning_match[last])
else:
    print('P')
BOT TESTING:
The following script can be used to ensure your input-output will be understood by the matchmaker.  This is NOT the code that will actually perform the matchmaking, but the arguments and output of your bot will be the same.
bot_name = 'YOUR_SCRIPT_HERE.py'

import subprocess as sp

base_args = ['python', bot_name]
previous_runs = []
for run in range(5):
    args = base_args + previous_runs
    bot = sp.run(args, stdout=sp.PIPE)
    response = bot.stdout.decode().strip()
    
    print('Round #{0}:'.format(run))
    print('Response Received: {0}'.format(response))
    print('Response Understood: {0}'.format(response in 'RPS'))
    print()
    
    # let's pretend your opponent always does Paper...
    previous_runs.append('{0}{1}'.format(response, 'P'))



RE: Forum-wide Competition: Rock Paper Scissors [Meta-thread] - micseydel - Oct-08-2016

Is there any reason that this, in principal, is anything more than random guessing or coding specifically against a "strategy" an opposing script uses?


RE: Forum-wide Competition: Rock Paper Scissors [Meta-thread] - nilamo - Oct-08-2016

Planning for a specific strategy would probably net you an overall loss, as everything else could crush you.  Besides the commonly recognized strategies, there's trying to figure out which opponent you're facing so you can beat them, all while they're doing the same thing.  Do you pretend to be one of the older bots to lure your opponent into trying to counter-play you, only to switch what you're doing later on to counter their counter?

That said, a random guess could easily take the world by storm.  Go right ahead and submit that if you like :p


RE: Forum-wide Competition: Rock Paper Scissors [Meta-thread] - ichabod801 - Oct-08-2016

Randy:

import random

print(random.choice('RPS'))



RE: Forum-wide Competition: Rock Paper Scissors [Meta-thread] - ichabod801 - Oct-08-2016

Sirius:

"""
sirius.py

A serious attempt at a Rock-Paper-Scissors bot.
"""

import collections, random, sys

# initial responses, assuming random play
responses = {}
for me in 'RPS':
    for them in 'RPS':
        responses[me + them] = collections.Counter('RPS')

# update based on history of plays
history = sys.argv[1:]
for history_index in range(1, len(history)):
    state = history[history_index - 1]
    response = history[history_index][1]
    responses[state].update(response)

# get the current state
if history:
    state = history[-1]
else:
    state = 'RP'

# guess the opponent's next move
guess = random.choice(list(responses[state].elements()))

# play what would beat that guess
beat = dict(zip('RPS', 'PSR'))
print(beat[guess])
The idea here is that if you give it random noise, it will spit random noise back at you. However, if you give it a pattern it will beat you over the head with it.


RE: Forum-wide Competition: Rock Paper Scissors [Meta-thread] - wavic - Oct-08-2016

This define a set(), right? But you initiate as a dictionary. Or I am wrong?
responses = {}
for me in 'RPS':
   for them in 'RPS':
       responses[me + them] = collections.Counter('RPS')
This is  confusing. If I call the set variable in the interpreter it is surrounded with {} but this defines an empty dict  :dodgy:


RE: Forum-wide Competition: Rock Paper Scissors [Meta-thread] - ichabod801 - Oct-08-2016

(Oct-08-2016, 10:13 PM)wavic Wrote: This define a set(), right? But you initiate as a dictionary. Or I am wrong?
responses = {}
for me in 'RPS':
    for them in 'RPS':
        responses[me + them] = collections.Counter('RPS')

The responses variable is not a set, it is a dictionary ({} is the literal for an empty dictionary). The keys of responses are all the possible two-player plays: RR, RP, RS, PR, PP, PS, SR, SP, and SS. It tracks what the opponent did after each round, based on what was played in that round. So if the previous round was RP, and the opponent plays S, responses['RP'] gets one added to S. That means the next time the previous round was RP, Sirius will be more likely to guess that the opponent will play S, and therefore Sirius will be more likely to play R.


RE: Forum-wide Competition: Rock Paper Scissors [Meta-thread] - nilamo - Oct-12-2016

Ok, I won't delay this any longer.  I was trying to tweak it to try to out-smart Ichabod's sirius bot, but it's taken longer than I thought, and I said I'd add a little something to help kick us off, so here's my entry.

The first thing to note, is that it follows a set strategy very stringently.  Three strategies, in fact.  So the first thing it does when it's called, is figure out if it's in the middle of a strategy, and if it is, it responds with the next step in that strategy.  If it's NOT in the middle of a strategy (ie, every 3rd round), then it calculates it's current performance, and bases it's next strategy off of that.  So if it's losing, it uses one strat, if it's winning, a different one, and if the scores are perfectly tied (ie: the very first round) a third strategy.  In general, I like this approach, so I'll probably continue to tweak it to try to figure out a way around ichabod's predictive analysis.

# nilabot - nilamo
import sys

winning_match = {'R':'S', 'P':'R', 'S':'P'}
lookup = {}
# indexes match our current record... 0=tied, 1=winning, -1=losing
strategies = ['PRP', 'RSR', 'SPR']

''' lookup will end up looking like...
        RR: 0, RS: 1, RP: -1
    for all 9 combinations.
    0 is a tie, -1 a loss, +1 a win
'''
def populate_lookup():
    for left, loser in winning_match.items():
        for right in winning_match.keys():
            result = 0
            if right == loser:
                result = 1
            elif left != right:
                result = -1
            lookup[left+right] = result
    
def current_results(rounds):
    return sum(lookup[round] for round in rounds)

def current_strategy(rounds):
    current_strategy_length = len(rounds) % 3
    if current_strategy_length:
        strat_starts_with = rounds[-current_strategy_length][0]
        for strategy in strategies:
            if strategy.startswith(strat_starts_with):
                return strategy[current_strategy_length]
    return None
    
def get_response(rounds):
    strategy = current_strategy(rounds)
    # if we're in the middle of a strategy, keep going with it
    if strategy:
        return strategy
    # find out which strategy we should start next
    populate_lookup()
    record = current_results(rounds)
    ndx = 0 if record == 0 else 1 if record > 0 else -1
    
    response = strategies[ndx][0]
    return response
    
if __name__ == '__main__':
    rounds = sys.argv[1:]
    print(get_response(rounds))



RE: Forum-wide Competition: Rock Paper Scissors [Meta-thread] - ichabod801 - Oct-12-2016

I note that my Sirius bot recalculates from the complete history every round, and your Nilabot recalculates from the complete history every round. Would it not be better to allow bots to store information? You could require each bot module to have an attribute get_move that when called with one parameter, returns either 'R', 'P', or 'S'. The parameter would be the last move by both players. The you could import each bot and repeatedly call bot.get_response(last_play).


RE: Forum-wide Competition: Rock Paper Scissors [Meta-thread] - nilamo - Oct-13-2016

See, I did something like that in the old forums, and an enterprising member of the forums decided to create a bot that used the inspect module to crawl up the call stack to find out exactly what the other bot is doing. It didn't need to calculate anything, or make any sort of assumptions based on previous rounds, because it actually *knew* what the other bot was doing, and thus won every single time.

Recalculating based on history, and avoiding imports, was my lame attempt at bypassing that sort of shenanigans right from the start.