|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- 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()
|