Zero to competitive bot

The RPSA Guide

Everything you need to go from a blank file to a strategy that competes for credits. Read this once and you'll know more than most new contestants.

The game

Rock beats scissors. Scissors beats paper. Paper beats rock. You play 2,000 rounds against another bot. Your score isn't your win rate — it's your credit balance when the match ends.

Every round you win earns a base credit. Every round you lose costs one. But credits are also affected by perks — special abilities bought at mid-match auctions that can amplify your gains, protect against losses, or disrupt your opponent. A strategy that wins 55% of rounds but ignores the economy can still lose to a strategy that wins 48% of rounds but plays the auction well.

Your first bot

A strategy is a Python class. The minimum you need is one method:

from rpsa_sdk import Strategy

class MyFirstBot(Strategy):
    def next_move(self) -> str:
        return "rock"  # play rock every time
This compiles. It will lose to almost any adaptive bot because it's completely predictable. But it passes validation and is a legitimate starting point.

Upload it, run it against the demo bots, and watch what happens. The goal at this stage is to get something deployed — you can improve it from there.

Tracking your opponent

Most improvements come from one insight: opponents have patterns. If someone plays rock 60% of the time, you should play paper 100% of the time. The SDK provides helpers to track this:

from rpsa_sdk import Strategy
from rpsa_sdk.helpers import MoveHistory, counter

class CounterMostCommon(Strategy):
    def __init__(self):
        self.history = MoveHistory()  # tracks last N opponent moves

    def notify(self, own_move: str, opp_move: str) -> None:
        self.history.record(opp_move)  # called after every round

    def next_move(self) -> str:
        return counter(self.history.most_common())  # beats their most frequent move

MoveHistory keeps a sliding window of recorded moves and provides most_common(), counts(), and last(n). counter(move) returns the move that beats the given move. This pattern — record in notify(), decide in next_move() — is the foundation of almost every competitive strategy.

Why capital matters

The economy runs on credits. You start each match with a base balance. You earn or lose credits each round based on the outcome. But mid-match, an auction opens: perks go to the highest bidder, and those perks can change the economics of the remaining rounds.

A perk like PowerPlay multiplies your credit gain for a window of rounds. If you win the auction and then win four rounds straight, the payoff can be 3× what you'd have earned unperked. This means a strategy that loses more rounds but holds reserves for the right auction can finish higher than a strategy with a better win rate. To participate, set uses_context = True and use the injected Context to check the market and place bids. See the SDK docs for the full API.

Testing locally

Before uploading, test your strategy against the built-in bots using the offline harness:

from rpsa_sdk.harness import play
from rpsa_sdk.examples.always_rock import AlwaysRock

transcript = play(MyFirstBot(), AlwaysRock(), rounds=100)
print(transcript.wins_a, transcript.draws, transcript.wins_b)
# → 0 0 100  (rock loses to... nothing, draws with rock)

The harness uses the same RPS resolution as the live engine and calls notify() after every round so history-tracking strategies work correctly. Pass a seed for reproducible results:

t1 = play(MyBot(), RandomBot(), rounds=500, seed=42)
t2 = play(MyBot(), RandomBot(), rounds=500, seed=42)
assert t1.wins_a == t2.wins_a  # identical

You can also run the local validator to catch issues before upload:

python -m rpsa_sdk.validate my_strategy.py

This runs the same three-stage check the server uses: static security scan, 100-round smoke test, and context test (if uses_context = True).

Uploading and iterating

When you're ready, upload your strategy file from the upload page. The platform validates it automatically (usually under 2 minutes) and shows the result on the strategy detail page.

After validation passes, enroll the strategy in the current season and activate it as your official entry. Then use the demo arena to run exhibition matches against the built-in bots at any time — no season required.

Ready to go deeper?

The SDK docs cover the full economy API, all helper utilities, and the Tier B/C ML training workflow.