Browse Source

Simulator complete

main
Noëlle 1 year ago
commit
606f4019aa
No known key found for this signature in database
2 changed files with 274 additions and 0 deletions
  1. 56
    0
      README.md
  2. 218
    0
      potato_game.py

+ 56
- 0
README.md View File

@@ -0,0 +1,56 @@
# Simulator for Potato, a one-page RPG
See https://twitter.com/deathbybadger/status/1567425842526945280 for details.

**Potato** is a single-player, one-page RPG where you play as a halfling,
farming and harvesting potatoes while the Dark Lord amasses his forces
elsewhere in the world.

You have three scores: DESTINY, POTATOES, and ORCS. Each round, you
roll d6 to determine which type of event you experience: a day in the
garden, a knock at the door, or a darkening of the world.

For the first two, you then roll a second d6 to determine how your
scores change; the third event makes it cost more potatoes to get rid
of orcs. (At the beginning of the game, you can spend 1 potato to
reduce your ORCS score by 1.) If any score reaches 10, the game ends
with that score's ending; if DESTINY and ORCS reach 10 in the same
round, ORCS wins, and if DESTINY and POTATOES reach 10 in the same
round, you get a multi-win. (ORCS and POTATOES can never reach 10 in
the same round.)

When the simulator has simulated the requested number of games, it will
report its results: the number and percentage of wins for each score
type, and the mean and median number of rounds it took to win a game.

## Game Options

-v, --verbose Print detailed information about each game.
This gets very long; probably don't use it if
you're asking for a large number of games!

-n, --numruns [N] The number of games to simulate. Default: 10000

-m, --multiwin Announce on the console when a game ends in a
multi-win. This can lead to a lot of output.

-i, --instructions Print these instructions.

### Mutually Exclusive Options:

-a, --alt Use Wanderer's algorithm for determining when
to spend potatoes to reduce orcs. This spends
a potato if ORCS could win this round and
POTATOES could not, OR if ORCS is greater than
one and a 1d10 comes up less than the ORCS
score. It will only spend a potato if there
are enough potatoes to spend.

-p, --potatoper [N] Use the Potato-Per algorithm for determining
when to spend potatoes to reduce orcs. This
spends a potato when the ratio of POTATOES to
ORCS rises above a certain amount, modified by
the POTATOES cost to reduce ORCS by 1. It will
only spend a potato if there are enough
potatoes to spend.
""")

+ 218
- 0
potato_game.py View File

@@ -0,0 +1,218 @@
import argparse, random
from math import floor
from statistics import mean, median

class PotatoGame:
def __init__(self, min_potatoes_per_orc=None, alternate_po_mechanic=False, verbose=False):
self.potatoes = 0
self.destiny = 0
self.orcs = 0
self.potatoes_per_orc = 1
self.min_potatoes = min_potatoes_per_orc
self.default_game_state = [0, 0, 0]
self.alternate_po_mechanic = alternate_po_mechanic
self.verbose = verbose
self.rounds = 0

def vprint(self, message):
if self.verbose:
print(message)

def event(self):
self.rounds += 1
if self.alternate_po_mechanic and self.potatoes >= self.potatoes_per_orc:
if (self.orcs >= 8 \
and self.potatoes < 8) \
or (self.orcs > 0 \
and random.randint(1, 10) < (self.orcs + 1)):
self.vprint("Removing an orc.")
self.remove_orc()
if not self.alternate_po_mechanic \
and (self.min_potatoes and self.orcs > 0) \
and ((self.potatoes / self.potatoes_per_orc)/self.orcs >= self.min_potatoes):
self.vprint(f"Removing an orc. Potatoes, PPO, orcs, ratio: {self.potatoes, self.potatoes_per_orc, self.orcs, self.min_potatoes}")
self.remove_orc()
roll = random.randint(1,3)
if roll == 1:
self.vprint("In the garden...")
self.garden()
elif roll == 2:
self.vprint("A knock at the door...")
self.door()
else:
self.vprint("The world becomes a darker, more dangerous place...")
self.darken()
self.potatoes = self.potatoes if self.potatoes >= 0 else 0
self.destiny = self.destiny if self.destiny >= 0 else 0
self.orcs = self.orcs if self.orcs >=0 else 0
game_state = [[0, 0, 0], self.rounds]
if self.destiny >= 10:
game_state[0][0] = 1
if self.potatoes >= 10:
game_state[0][1] = 1
if self.orcs >= 10:
game_state[0][2] = 1
if game_state[0] == [1, 0, 1] or game_state[0] == [0, 1, 1]:
game_state[0] = [0, 0, 1]
if game_state[0] != self.default_game_state:
self.vprint(f"Game over! Game state: {game_state}")
return game_state
def remove_orc(self):
self.potatoes -= self.potatoes_per_orc
self.orcs -= 1


def garden(self):
roll = random.randint(1,6)
if roll == 1:
self.potatoes += 1
elif roll == 2:
self.potatoes += 1
self.destiny += 1
elif roll == 3:
self.destiny += 1
self.orcs += 1
elif roll == 4:
self.orcs += 1
self.potatoes -= 1
elif roll == 5:
self.potatoes -= 1
else:
self.potatoes += 2
def door(self):
roll = random.randint(1,6)
if roll == 1:
self.orcs += 1
elif roll == 2:
self.destiny += 1
elif roll == 3:
self.destiny += 1
self.orcs += 1
elif roll == 4:
self.orcs += 1
self.potatoes -= 1
elif roll == 5:
self.destiny += 1
else:
self.potatoes += 2

def darken(self):
self.potatoes_per_orc += 1

def instructions():
print("""
Simulator for Potato, a one-page RPG
See https://twitter.com/deathbybadger/status/1567425842526945280
for details.

Potato is a single-player, one-page RPG where you play as a halfling,
farming and harvesting potatoes while the Dark Lord amasses his forces
elsewhere in the world.

You have three scores: DESTINY, POTATOES, and ORCS. Each round, you
roll d6 to determine which type of event you experience: a day in the
garden, a knock at the door, or a darkening of the world.

For the first two, you then roll a second d6 to determine how your
scores change; the third event makes it cost more potatoes to get rid
of orcs. (At the beginning of the game, you can spend 1 potato to
reduce your ORCS score by 1.) If any score reaches 10, the game ends
with that score's ending; if DESTINY and ORCS reach 10 in the same
round, ORCS wins, and if DESTINY and POTATOES reach 10 in the same
round, you get a multi-win. (ORCS and POTATOES can never reach 10 in
the same round.)

When the simulator has simulated the requested number of games, it will
report its results: the number and percentage of wins for each score
type, and the mean and median number of rounds it took to win a game.
""")
input("Press any key to continue. ")
print("""
Game Options

-v, --verbose Print detailed information about each game.
This gets very long; probably don't use it if
you're asking for a large number of games!

-n, --numruns [N] The number of games to simulate. Default: 10000

-m, --multiwin Announce on the console when a game ends in a
multi-win. This can lead to a lot of output.

-i, --instructions Print these instructions.

Mutually Exclusive Options:

-a, --alt Use Wanderer's algorithm for determining when
to spend potatoes to reduce orcs. This spends
a potato if ORCS could win this round and
POTATOES could not, OR if ORCS is greater than
one and a 1d10 comes up less than the ORCS
score. It will only spend a potato if there
are enough potatoes to spend.

-p, --potatoper [N] Use the Potato-Per algorithm for determining
when to spend potatoes to reduce orcs. This
spends a potato when the ratio of POTATOES to
ORCS rises above a certain amount, modified by
the POTATOES cost to reduce ORCS by 1. It will
only spend a potato if there are enough
potatoes to spend.
""")
return None


parser = argparse.ArgumentParser(description="Simulator for Potato, a one-page RPG.")
parser.add_argument("-v", "--verbose", help="Prints information about each run", action="store_true")
parser.add_argument("-n", "--numruns", help="Number of games to simulate", nargs="?", type=int, const=10000, default=10000)
parser.add_argument("-m", "--multiwin", help="Announce multi-wins", action="store_true")
parser.add_argument("-i", "--instructions", help="Print full instructions and exit", action="store_true")
potato_spending = parser.add_mutually_exclusive_group()
potato_spending.add_argument("-a", "--alt", help="Use Wanderer's potato-spending algorithm", action="store_true")
potato_spending.add_argument("-p", "--potatoper", help="Spend when I have this many potatoes per orc", type=int)

def main():
global parser
args = parser.parse_args()
if args.instructions:
return instructions()

vic_types = ["destiny", "potatoes", "orcs"]
game_wins = {k: {"wins": 0, "percent": 0} for k in vic_types}
rounds_counter = []
args.numruns = 1 if args.numruns < 1 else args.numruns
plural = "s" if args.numruns != 1 else ""
print(f"Running {args.numruns} game simulation{plural}", end="")
if args.potatoper:
print(f", using potatoes-per algorithm at {args.potatoper} potatoes per orc", end="")
if args.alt:
print(f", using Wanderer's potato-spending algorithm", end="")
if args.verbose:
print(", printing detailed information about each simulated game", end="")
print(".\n")

for _ in range(args.numruns):
pg = PotatoGame(min_potatoes_per_orc=args.potatoper, alternate_po_mechanic=args.alt, verbose=args.verbose)
game_end = None
while not game_end:
game_end = pg.event()
rich_game_end = [v for i, v in enumerate(vic_types) if game_end[0][i] == 1]
rounds_counter.append(game_end[1])
if args.multiwin and sum(game_end[0]) > 1:
print(f"Multi-win! {rich_game_end}")
for v in rich_game_end:
game_wins[v]["wins"] = game_wins[v]["wins"] + 1

tallies = []
for k, v in game_wins.items():
game_wins[k]["percent"] = f"{floor((v['wins'] / args.numruns) * 10000)/100}%"
tallies.append(f"{k.capitalize()}: {v['wins']} wins ({v['percent']} percent)")
rounds_tally = f"Rounds per game: average {floor(mean(rounds_counter)*100)/100}, median {median(rounds_counter)}"
tally_print = "Final tally:\n\t" + '\n\t'.join(tallies) + "\n\t" + rounds_tally
print(tally_print)

if __name__ == "__main__":
main()

Loading…
Cancel
Save