Aenimus

Pythonic Simulator for Level 10 Quest

Dec 13th, 2019
266
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.93 KB | None | 0 0
  1. import collections
  2. import random
  3. import math
  4. import statistics
  5.  
  6. class Utils:
  7.  
  8. def log(message):
  9. print(message)
  10.  
  11.  
  12. class Simulator():
  13.  
  14. def level_10_quest1(self, player_state):
  15. basement = Basement()
  16. while basement.progress < 1:
  17. basement.resolve_turn(player_state)
  18. return player_state
  19.  
  20. def level_10_quest3(self, player_state):
  21. topfloor = TopFloor()
  22. while topfloor.progress < 1:
  23. topfloor.resolve_turn(player_state)
  24. return player_state
  25.  
  26. def run_simulator(self, iterations = 100000):
  27. turns1 = []
  28. turns3 = []
  29.  
  30. for a in range(iterations):
  31. player_state = self.level_10_quest1(PlayerState())
  32. turns1.append(player_state.total_turns_spent)
  33.  
  34. if player_state.stench_jellies:
  35. pass
  36. elif player_state.get_amulet():
  37. Utils.log("In {} instances at {}% +NC, with an amulet, it took an average of {} turns to complete the basement, with a median of {} and a deviation of {}."
  38. .format(iterations, PlayerState().player_nc, statistics.mean(turns1), statistics.median(turns1), statistics.pstdev(turns1)))
  39. else:
  40. Utils.log("In {} instances at {}% +NC, with no amulet, it took an average of {} turns to complete the basement, with a median of {} and a deviation of {}."
  41. .format(iterations, PlayerState().player_nc, statistics.mean(turns1), statistics.median(turns1), statistics.pstdev(turns1)))
  42.  
  43. for a in range(iterations):
  44. player_state = self.level_10_quest3(PlayerState())
  45. turns3.append(player_state.total_turns_spent)
  46.  
  47. if player_state.stench_jellies:
  48. pass
  49. elif player_state.get_mohawk_wig():
  50. Utils.log("In {} instances at {}% +NC, with a mohawk wig, it took an average of {} turns to complete the top floor, with a median of {} and a deviation of {}."
  51. .format(iterations, PlayerState().player_nc, statistics.mean(turns3), statistics.median(turns3), statistics.pstdev(turns3)))
  52. else:
  53. Utils.log("In {} instances at {}% +NC, with no mohawk wig, it took an average of {} turns to complete the top floor, with a median of {} and a deviation of {}."
  54. .format(iterations, PlayerState().player_nc, statistics.mean(turns3), statistics.median(turns3), statistics.pstdev(turns3)))
  55.  
  56. class PlayerState():
  57. def __init__(self):
  58. self.player_nc = 25 # CHANGE NC HERE
  59. self.player_item = 700 # CHANGE +ITEM% HERE
  60. self.total_turns_spent = 0
  61. self.wishes = 3
  62. self.banishes = [
  63. Banish("Spring Bumper", 30, 999, True, True),
  64. Banish("Throw Latte", 30, 4, True, True),
  65. Banish("Reflex hammer", 30, 3, False, True),
  66. Banish("KGB dart", 20, 3, False, True),
  67. Banish("Batter Up!", 9999, 999, False, False),
  68. ]
  69. self.olfacted_mob = None
  70. self.latted_mob = None
  71. self.mated_mob = None
  72. self.stench_jellies = 0
  73. self.food_quality = 25
  74. self.massive_dumbbell = 0
  75. self.bass_record = 0
  76. self.model_rocketship = 0
  77. self.mohawk_wig = 1
  78. self.amulet = 1
  79. self.grops_encounters = 0
  80. self.smoke_bombs = 0
  81.  
  82. def nc_mod(self):
  83. return mod_cap(self.player_nc)
  84.  
  85. def item_mod(self):
  86. return 1 + (self.player_item/100)
  87.  
  88. def get_total_turns_spent(self):
  89. return self.total_turns_spent
  90.  
  91. def get_wishes(self):
  92. return self.wishes
  93.  
  94. def get_massive_dumbbell(self):
  95. return self.massive_dumbbell
  96.  
  97. def get_bass_record(self):
  98. return self.bass_record
  99.  
  100. def get_model_rocketship(self):
  101. return self.model_rocketship
  102.  
  103. def get_amulet(self):
  104. return self.amulet
  105.  
  106. def get_mohawk_wig(self):
  107. return self.mohawk_wig
  108.  
  109. def get_extra_copies(self, encounter_name):
  110. return (
  111. 0 +
  112. (3 if self.olfacted_mob == encounter_name else 0) +
  113. (2 if self.latted_mob == encounter_name else 0) +
  114. (1 if self.mated_mob == encounter_name else 0)
  115. )
  116.  
  117. def need_sniffs(self, encounter):
  118. if encounter.get_use_all_sniffs():
  119. self.olfacted_mob = encounter.name
  120. self.latted_mob = encounter.name
  121. self.mated_mob = encounter.name
  122. else:
  123. if encounter.use_olfact:
  124. self.olfacted_mob = encounter.name
  125. if encounter.use_latte:
  126. self.latted_mob = encounter.name
  127. if encounter.use_mating:
  128. self.mated_mob = encounter.name
  129.  
  130. def check_banish(self, encounter):
  131. avail_banish = None
  132. for banish in self.banishes:
  133. if banish.get_banished_mob() == encounter.name:
  134. return False
  135. if (not avail_banish) and (banish.check(self)):
  136. avail_banish = banish
  137. return avail_banish
  138.  
  139. def use_banish(self, location, encounter):
  140. banish = self.check_banish(encounter)
  141. if banish:
  142. banish.use(self, location, encounter)
  143. return True
  144. return False
  145.  
  146. def mod_cap(virgin_mod):
  147. if virgin_mod < 0:
  148. return 0
  149. if virgin_mod > 25:
  150. return 20 + math.floor(virgin_mod/5)
  151. return virgin_mod
  152.  
  153. class Banish():
  154. def __init__(self, name = "", length = 30, avail_uses = 3, cooldown = False, free = True):
  155. self.name = name
  156. self.length = length
  157. self.avail_uses = avail_uses
  158. self.cooldown = cooldown
  159. self.free = free
  160. self.banished_mob = None
  161. self.expiry = -1
  162.  
  163. def get_avail_uses(self):
  164. return self.avail_uses
  165.  
  166. def get_expiry(self):
  167. return self.expiry
  168.  
  169. def get_banished_mob(self):
  170. if self.get_expiry() < player_state.get_total_turns_spent():
  171. return None
  172. return self.banished_mob
  173.  
  174. def check(self, player_state):
  175. return (self.get_avail_uses()) and (self.get_expiry() < player_state.get_total_turns_spent())
  176.  
  177. def use(self, location, player_state, encounter):
  178. self.banished_mob = encounter.name
  179. self.avail_uses -= 1
  180. self.expiry = player_state.get_total_turns_spent() + self.length
  181. location.banishes_used += 1
  182.  
  183.  
  184. class Encounter():
  185. def __init__(self, name = "", banish = False, itembase = 0):
  186. self.name = name
  187. self.wish = False
  188. self.should_banish = banish
  189. self.use_all_sniffs = False
  190. self.use_olfact = False
  191. self.use_latte = False
  192. self.use_mating = False
  193.  
  194. def __str__(self):
  195. return "Encounter({})".format(self.name)
  196.  
  197. def get_use_all_sniffs(self):
  198. return self.use_all_sniffs
  199.  
  200. def check(self, location, player_state):
  201. return True
  202.  
  203. def add_nc_queue(self, location, nc = None):
  204. if nc is None:
  205. nc = self.name
  206. location.nc_history.append(nc)
  207.  
  208. def add_com_queue(self, location, combat = None):
  209. if combat is None:
  210. combat = self.name
  211. location.combat_history.append(combat)
  212.  
  213. def run(self, location, player_state):
  214. if self.should_banish:
  215. player_state.use_banish(location, encounter)
  216. return True
  217. location.turns_spent += 1
  218. location.pity_timer += 1
  219. player_state.total_turns_spent += 1
  220.  
  221. class Location():
  222. def __init__(self, native_nc, superlikelies, non_combats, combats):
  223. self.native_nc = native_nc
  224. self.superlikelies = superlikelies
  225. self.non_combats = non_combats
  226. self.combats = combats
  227. self.nc_history = collections.deque([], 5)
  228. self.combat_history = collections.deque([], 5)
  229. self.banishes_used = 0
  230. self.turns_spent = 0
  231.  
  232. def select_encounter(self, player_state):
  233. encounter = self.check_superlikely(player_state)
  234. if encounter is None:
  235. encounter = self.select_nc(player_state)
  236. if encounter is None:
  237. encounter = self.select_combat(player_state)
  238. return encounter
  239.  
  240. def check_superlikely(self, player_state):
  241. for superlikely in self.superlikelies:
  242. if superlikely.check(self, player_state):
  243. return superlikely
  244. return None
  245.  
  246. def weighted_random(self, weights):
  247. total = sum(weight for item, weight in weights.items())
  248. r = random.randint(1, total)
  249. for (item, weight) in weights.items():
  250. r -= weight
  251. if r <= 0:
  252. return item
  253.  
  254. def get_nc_weights(self, player_state):
  255. nc_weights = {}
  256. for name in [x.name for x in self.non_combats if x.check(self, player_state)]:
  257. if not name in nc_weights.keys():
  258. copies = 1 #+ player_state.get_extra_copies(name)
  259. nc_weights[name] = copies if (name in self.nc_history) else (4 * copies)
  260. else:
  261. nc_weights[name] += 1 if (name in self.nc_history) else 4
  262. return nc_weights
  263.  
  264. def nc_queue(self, nc):
  265. if (nc.name in self.nc_history) and (random.randrange(4)):
  266. return None
  267. return nc
  268.  
  269. def select_nc(self, player_state):
  270. if self.native_nc == 0:
  271. return None
  272. actual_nc = self.native_nc + player_state.player_nc
  273. avail_ncs = [x for x in self.non_combats if x.check(self, player_state)]
  274. if (len(avail_ncs)) and ((random.randrange(100) < actual_nc) or (self.pity_timer == 10)):
  275. encounter = None
  276. while encounter is None:
  277. encounter = self.nc_queue(random.choice(avail_ncs))
  278. return encounter
  279. return None
  280.  
  281. def get_combat_weights(self, player_state):
  282. combat_weights = {}
  283. for name in [x.name for x in self.combats if x.check(self, player_state)]:
  284. if not name in combat_weights.keys():
  285. copies = 1 + player_state.get_extra_copies(name)
  286. combat_weights[name] = copies if (name in self.combat_history and not player_state.olfacted_mob == name) else (4 * copies)
  287. else:
  288. combat_weights[name] += 1 if (name in self.combat_history and not player_state.olfacted_mob == name) else 4
  289. return combat_weights
  290.  
  291. def select_combat(self, player_state):
  292. encounter_name = self.weighted_random(self.get_combat_weights(player_state))
  293. return [x for x in self.combats if x.name == encounter_name][0]
  294.  
  295. def resolve_turn(self, player_state):
  296. encounter = None
  297. loops = 0
  298. while (encounter is None) and (loops < 100):
  299. encounter = self.select_encounter(player_state)
  300. loops += 1
  301. if encounter is not None:
  302. encounter.run(self, player_state)
  303.  
  304.  
  305. class Basement(Location):
  306. class Furry(Encounter):
  307. def __init__(self, name = ""):
  308. self.name = name
  309.  
  310. def run(self, location, player_state):
  311. if player_state.get_massive_dumbbell():
  312. location.progress = 1
  313. location.turns_spent += 1
  314. location.pity_timer = 0
  315. player_state.total_turns_spent += 1
  316. self.add_nc_queue(location)
  317. return True
  318. elif player_state.get_amulet():
  319. location.progress = 1
  320. else:
  321. player_state.massive_dumbbell += 1
  322. location.turns_spent += 1
  323. location.pity_timer = 0
  324. player_state.total_turns_spent += 1 #nothing added to queue because taking the superlikely is optimal.
  325.  
  326. class Source(Encounter):
  327. def __init__(self, name = ""):
  328. self.name = name
  329.  
  330. def run(self, location, player_state):
  331. if player_state.get_massive_dumbbell():
  332. location.progress = 1
  333. self.add_nc_queue(location)
  334. elif player_state.get_amulet():
  335. location.progress = 1 #nothing added to queue because taking the superlikely (in case).
  336. else:
  337. player_state.massive_dumbbell += 1
  338. location.turns_spent += 1
  339. location.pity_timer = 0
  340. player_state.total_turns_spent += 1 #nothing added to queue because taking the superlikely is optimal.
  341.  
  342. class Gym(Encounter):
  343. def __init__(self, name = ""):
  344. self.name = name
  345.  
  346. def run(self, location, player_state):
  347. if player_state.get_massive_dumbbell():
  348. location.pity_timer = 0
  349. return False
  350. elif player_state.get_amulet():
  351. location.progress = 1
  352. else:
  353. player_state.massive_dumbbell += 1
  354. location.turns_spent += 1
  355. location.pity_timer = 0
  356. player_state.total_turns_spent += 1
  357. self.add_nc_queue(location)
  358.  
  359. def __init__(self):
  360. Location.__init__(
  361. self,
  362. 5,
  363. [],
  364. [ Basement.Furry("Furry"),
  365. Basement.Source("Source"),
  366. Basement.Gym("Gym")
  367. ],
  368. [
  369. Encounter("Alphabet Giant"),
  370. Encounter("Fitness Giant"),
  371. Encounter("Furry Giant"),
  372. Encounter("Neckbeard Giant")
  373. ]
  374. )
  375. self.progress = 0
  376. self.pity_timer = 0
  377.  
  378. def get_progress(self):
  379. return self.progress
  380.  
  381.  
  382. class TopFloor(Location):
  383. class Steam(Encounter):
  384. def __init__(self, name = ""):
  385. self.name = name
  386.  
  387. def run(self, location, player_state):
  388. if player_state.get_bass_record():
  389. location.progress = 1
  390. elif player_state.get_model_rocketship():
  391. location.pity_timer = 0
  392. return True;
  393. else:
  394. player_state.model_rocketship += 1
  395. location.turns_spent += 1
  396. location.pity_timer = 0
  397. player_state.total_turns_spent += 1
  398. self.add_nc_queue(location)
  399.  
  400. class Goth(Encounter):
  401. def __init__(self, name = ""):
  402. self.name = name
  403.  
  404. def run(self, location, player_state):
  405. if player_state.get_bass_record():
  406. location.progress = 1
  407. elif player_state.get_model_rocketship():
  408. location.pity_timer = 0
  409. return True;
  410. else:
  411. player_state.model_rocketship += 1
  412. location.turns_spent += 1
  413. location.pity_timer = 0
  414. player_state.total_turns_spent += 1
  415. self.add_nc_queue(location)
  416.  
  417. class Punk(Encounter):
  418. def __init__(self, name = ""):
  419. self.name = name
  420.  
  421. def run(self, location, player_state):
  422. if player_state.get_mohawk_wig():
  423. location.progress = 1
  424. location.turns_spent += 1
  425. location.pity_timer = 0
  426. player_state.total_turns_spent += 1
  427. self.add_nc_queue(location)
  428. elif not player_state.get_bass_record():
  429. player_state.bass_record += 1
  430. location.turns_spent += 1
  431. location.pity_timer = 0
  432. player_state.total_turns_spent += 1
  433. self.add_nc_queue(location)
  434. else:
  435. location.pity_timer = 0
  436. return True;
  437.  
  438. class Raver(Encounter):
  439. def __init__(self, name = ""):
  440. self.name = name
  441.  
  442. def run(self, location, player_state):
  443. if player_state.get_mohawk_wig():
  444. location.progress = 1
  445. location.turns_spent += 1
  446. location.pity_timer = 0
  447. player_state.total_turns_spent += 1
  448. self.add_nc_queue(location)
  449. elif not player_state.get_bass_record():
  450. player_state.bass_record += 1
  451. location.turns_spent += 1
  452. location.pity_timer = 0
  453. player_state.total_turns_spent += 1
  454. self.add_nc_queue(location)
  455. else:
  456. pass
  457.  
  458. def __init__(self):
  459. Location.__init__(
  460. self,
  461. 5,
  462. [],
  463. [ TopFloor.Steam("Steam"),
  464. TopFloor.Goth("Goth"),
  465. TopFloor.Punk("Punk"),
  466. TopFloor.Raver("Raver")
  467. ],
  468. [
  469. Encounter("Goth Giant"),
  470. Encounter("Punk Rock Giant"),
  471. Encounter("Raver Giant"),
  472. Encounter("Steampunk Giant")
  473. ]
  474. )
  475. self.progress = 0
  476. self.pity_timer = 0
  477.  
  478. def get_progress(self):
  479. return self.progress
  480.  
  481. if __name__ == "__main__":
  482. Simulator().run_simulator()
Advertisement
Add Comment
Please, Sign In to add comment