A save state/password generator for the original Metroid.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

metananas.py 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  1. # Metroid (NES) Random Password Generator
  2. # Author: Noëlle Anthony
  3. # License: MIT
  4. # Date: October 2019
  5. # This uses http://games.technoplaza.net/mpg/password.txt as a basis for its password algorithm
  6. import random, sys
  7. from ananas import PineappleBot, hourly
  8. class MetroidState:
  9. """ Stores the game state
  10. """
  11. def __init__(self):
  12. # Alphabet is 64 characters - 6 bits per character
  13. self.alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz?-"
  14. # The password has different flags for "item available for pickup" and
  15. # "Samus has the item". I'm keeping them separate, but when the generator
  16. # selects an item as "picked up", that means Samus has it and it's not available.
  17. self.itemsCollected = {
  18. "Maru Mari": False,
  19. "Bombs": False,
  20. "Long Beam": False,
  21. "Ice Beam": False,
  22. "Wave Beam": False,
  23. "High Jump Boots": False,
  24. "Varia": False,
  25. "Screw Attack": False
  26. }
  27. self.samusHas = {
  28. "Maru Mari": False,
  29. "Bombs": False,
  30. "Long Beam": False,
  31. "Ice Beam": False,
  32. "Wave Beam": False,
  33. "High Jump Boots": False,
  34. "Varia": False,
  35. "Screw Attack": False
  36. }
  37. # Missile tanks are listed in the order in which they appear in the password,
  38. # NOT in zone order or in any reasonable collection order.
  39. self.missileTanks = {
  40. 1: False,
  41. 2: False,
  42. 3: False,
  43. 4: False,
  44. 5: False,
  45. 6: False,
  46. 7: False,
  47. 8: False,
  48. 9: False,
  49. 10: False,
  50. 11: False,
  51. 12: False,
  52. 13: False,
  53. 14: False,
  54. 15: False,
  55. 16: False,
  56. 17: False,
  57. 18: False,
  58. 19: False,
  59. 20: False,
  60. 21: False
  61. }
  62. # Likewise energy tanks are listed in password order.
  63. self.energyTanks = {
  64. 1: False,
  65. 2: False,
  66. 3: False,
  67. 4: False,
  68. 5: False,
  69. 6: False,
  70. 7: False,
  71. 8: False
  72. }
  73. # This may be left-to-right (Samus approaches from the right). I haven't checked.
  74. self.zebetitesDestroyed = {
  75. 1: False,
  76. 2: False,
  77. 3: False,
  78. 4: False,
  79. 5: False
  80. }
  81. # I'm not sure why I decided to segregate these by zone, except that that's how
  82. # truepeacein.space does it.
  83. self.doors = {
  84. "Brinstar": {
  85. 1: False,
  86. 2: False,
  87. 3: False,
  88. 4: False,
  89. 5: False
  90. }, "Norfair": {
  91. 1: False,
  92. 2: False,
  93. 3: False,
  94. 4: False
  95. }, "Kraid": {
  96. 1: False,
  97. 2: False,
  98. 3: False,
  99. 4: False,
  100. 5: False
  101. }, "Ridley": {
  102. 1: False,
  103. 2: False
  104. }, "Tourian": {
  105. 1: False,
  106. 2: False,
  107. 3: False
  108. }
  109. }
  110. # The next three are self-explanatory.
  111. self.kraidKilled = False
  112. self.ridleyKilled = False
  113. self.motherBrainKilled = False
  114. # The Kraid and Ridley statues rise when Kraid and Ridley are killed, but
  115. # their states are stored separately in the password. It's possible to
  116. # raise them without killing the bosses, granting early access to Tourian.
  117. self.kraidStatue = False
  118. self.ridleyStatue = False
  119. # Is Samus wearing her armor (False) or her swimsuit (True)?
  120. self.swimsuit = False
  121. # 0-255. You can have more missiles than 5*collected tanks (in fact, you
  122. # can only collect 21 tanks - thus 105 missiles - but can have up to 255
  123. # missiles in your inventory).
  124. self.missileCount = 0
  125. # How advanced is the game clock? After 3 hours you don't get the good ending.
  126. self.gameAge = 0
  127. # There are five possible start locations: Brinstar, where you start, and
  128. # at the bottom of the elevator where you enter each subsequent zone.
  129. self.locations = ["Brinstar", "Norfair", "Kraid's Lair", "Ridley's Lair", "Tourian"]
  130. self.startLocation = 0
  131. # Arrays to store the 144 bits that compose the password
  132. self.bitfield = []
  133. self.initializeBitfield()
  134. self.fullbitfield = []
  135. def initializeBitfield(self):
  136. """ Set the first 128 bits of the bitfield to 0.
  137. The remaining 16 bits will be set later.
  138. """
  139. self.bitfield = []
  140. for _ in range(128):
  141. self.bitfield.append(0)
  142. def toggleItem(self, itm):
  143. """ Mark an item as collected or uncollected.
  144. """
  145. if itm in self.itemsCollected.keys():
  146. self.itemsCollected[itm] = not self.itemsCollected[itm]
  147. self.samusHas[itm] = not self.samusHas[itm]
  148. else:
  149. print("Couldn't find item: {}".format(str(itm)))
  150. def toggleMissileTank(self, num):
  151. """ Mark a missile tank as collected or uncollected.
  152. """
  153. try:
  154. num = int(num)
  155. except:
  156. print("{} is not a number".format(num))
  157. return
  158. if num in self.missileTanks.keys():
  159. self.missileTanks[num] = not self.missileTanks[num]
  160. else:
  161. print("Couldn't find missile tank: {}".format(num))
  162. def toggleEnergyTank(self, num):
  163. """ Mark an energy tank as collected or uncollected.
  164. """
  165. try:
  166. num = int(num)
  167. except:
  168. print("{} is not a number".format(num))
  169. return
  170. if num in self.energyTanks.keys():
  171. self.energyTanks[num] = not self.energyTanks[num]
  172. else:
  173. print("Couldn't find energy tank: {}".format(num))
  174. def toggleZebetite(self, num):
  175. """ Mark a Zebetite stem as destroyed or intact.
  176. """
  177. try:
  178. num = int(num)
  179. except:
  180. print("{} is not a number".format(num))
  181. return
  182. if num in self.zebetitesDestroyed.keys():
  183. self.zebetitesDestroyed[num] = not self.zebetitesDestroyed[num]
  184. else:
  185. print("Couldn't find Zebetite: {}".format(num))
  186. def toggleKraid(self):
  187. """ Mark Kraid as killed or alive.
  188. """
  189. self.kraidKilled = not self.kraidKilled
  190. self.kraidStatue = self.kraidKilled
  191. def toggleKraidStatue(self):
  192. """ Mark Kraid's statue as raised or lowered.
  193. If Kraid is killed but his statue isn't raised, you can't complete the game.
  194. """
  195. self.kraidStatue = not self.kraidStatue
  196. if self.kraidKilled and not self.kraidStatue:
  197. print("WARNING: Kraid has been killed but his statue has not been raised.")
  198. def toggleRidley(self):
  199. """ Mark Ridley as killed or alive.
  200. """
  201. self.ridleyKilled = not self.ridleyKilled
  202. self.ridleyStatue = self.ridleyKilled
  203. def toggleRidleyStatue(self):
  204. """ Mark Ridley's statue as raised or lowered.
  205. If Ridley is killed but his statue isn't raised, you can't complete the game.
  206. """
  207. self.ridleyStatue = not self.ridleyStatue
  208. if self.ridleyKilled and not self.ridleyStatue:
  209. print("WARNING: Ridley has been killed but his statue has not been raised.")
  210. def toggleMotherBrain(self):
  211. """ Mark Mother Brain as killed or alive.
  212. If Mother Brain is marked as killed, the self-destruct timer won't go off
  213. when you reach her room.
  214. """
  215. self.motherBrainKilled = not self.motherBrainKilled
  216. def toggleDoor(self, area, door):
  217. """ Mark a given red/yellow door as opened or locked.
  218. """
  219. try:
  220. area = str(area)
  221. door = int(door)
  222. except:
  223. print("Area must be string, door must be a positive integer")
  224. return
  225. if area in self.doors.keys() and int(door) in self.doors[area].keys():
  226. self.doors[area][door] = not self.doors[area][door]
  227. else:
  228. print("Couldn't find door {} in area {}".format(door, area))
  229. def toggleSwimsuit(self):
  230. """ Determine whether or not Samus is wearing her armor.
  231. """
  232. self.swimsuit = not self.swimsuit
  233. def newLocation(self, loc):
  234. """ Set a new starting location (0-4).
  235. """
  236. try:
  237. loc = str(loc)
  238. except:
  239. print("Location must be a string")
  240. return
  241. if loc in self.locations:
  242. self.startLocation = self.locations.index(loc)
  243. else:
  244. print("Couldn't find location: {}".format(loc))
  245. def collectedItems(self):
  246. """ List which items have been collected.
  247. Under this generator's rules, if Samus doesn't have an item,
  248. it's available to be picked up.
  249. """
  250. o = []
  251. for k,v in self.itemsCollected.items():
  252. if v == True:
  253. o.append(k)
  254. if len(o) == 0:
  255. return "None"
  256. else:
  257. return ", ".join(o)
  258. def collectedMissiles(self):
  259. """ List which missile tanks have been collected.
  260. """
  261. o = []
  262. for k, v in self.missileTanks.items():
  263. if v == True:
  264. o.append(k)
  265. if len(o) == 0:
  266. return "None"
  267. else:
  268. return ", ".join([str(b) for b in o])
  269. def collectedEtanks(self):
  270. """ List which energy tanks have been collected.
  271. """
  272. o = []
  273. for k, v in self.energyTanks.items():
  274. if v == True:
  275. o.append(k)
  276. if len(o) == 0:
  277. return "None"
  278. else:
  279. return ", ".join([str(b) for b in o])
  280. def killedZebetites(self):
  281. """ List which Zebetite stems have been destroyed.
  282. """
  283. o = []
  284. for k, v in self.zebetitesDestroyed.items():
  285. if v == True:
  286. o.append(k)
  287. if len(o) == 0:
  288. return "None"
  289. else:
  290. return ", ".join([str(b) for b in o])
  291. def killedBosses(self):
  292. """ List which bosses have been killed.
  293. """
  294. o = []
  295. if self.kraidKilled:
  296. o.append("Kraid")
  297. if self.ridleyKilled:
  298. o.append("Ridley")
  299. if self.motherBrainKilled:
  300. o.append("Mother Brain")
  301. if len(o) == 0:
  302. return "None"
  303. else:
  304. return ", ".join(o)
  305. def raisedStatues(self):
  306. """ List which statues have been raised.
  307. """
  308. o = []
  309. if self.kraidStatue:
  310. o.append("Kraid")
  311. if self.ridleyStatue:
  312. o.append("Ridley")
  313. if len(o) == 0:
  314. return "None"
  315. else:
  316. return ", ".join(o)
  317. def inBailey(self):
  318. """ Show whether Samus is in her swimsuit or not.
  319. 'inBailey' refers to an old (false) explanation of the JUSTIN BAILEY
  320. password, in which a 'bailey' was English slang for a bathing suit,
  321. so with that password, Samus was "Just In Bailey" - i.e. in her swimsuit.
  322. """
  323. if self.swimsuit:
  324. return "Yes"
  325. else:
  326. return "No"
  327. def openedDoors(self):
  328. """ List which red/yellow doors have been unlocked.
  329. """
  330. d = {"Brinstar": 0, "Norfair": 0, "Kraid": 0, "Ridley": 0, "Tourian": 0}
  331. o = []
  332. for k,v in self.doors["Brinstar"].items():
  333. if v == True:
  334. d["Brinstar"] = d["Brinstar"] + 1
  335. for k,v in self.doors["Norfair"].items():
  336. if v == True:
  337. d["Norfair"] = d["Norfair"] + 1
  338. for k,v in self.doors["Kraid"].items():
  339. if v == True:
  340. d["Kraid"] = d["Kraid"] + 1
  341. for k,v in self.doors["Ridley"].items():
  342. if v == True:
  343. d["Ridley"] = d["Ridley"] + 1
  344. for k,v in self.doors["Tourian"].items():
  345. if v == True:
  346. d["Tourian"] = d["Tourian"] + 1
  347. for k, v in d.items():
  348. o.append("{} {}".format(k, v))
  349. return ", ".join(o)
  350. def getBits(self):
  351. """ Return the bitfield in an easily-readable format.
  352. """
  353. o = []
  354. word = []
  355. for i in range(128):
  356. word.append(str(self.bitfield[i]))
  357. if len(word) == 8:
  358. o.append("".join(word))
  359. word = []
  360. o1 = " ".join(o[:8])
  361. o2 = " ".join(o[8:])
  362. return o1 + "\n" + o2
  363. def toString(self):
  364. """ Output the game state as a newline-delimited string.
  365. """
  366. ic = "Items Collected: {}".format(self.collectedItems())
  367. mt = "Missile Tanks Collected: {}".format(self.collectedMissiles())
  368. et = "Energy Tanks Collected: {}".format(self.collectedEtanks())
  369. zb = "Zebetites Killed: {}".format(self.killedZebetites())
  370. kb = "Bosses Killed: {}".format(self.killedBosses())
  371. rs = "Statues Raised: {}".format(self.raisedStatues())
  372. sw = "Swimsuit?: {}".format(self.inBailey())
  373. sl = "Start Location: {}".format(self.locations[self.startLocation])
  374. dr = "Unlocked Doors: {}".format(self.openedDoors())
  375. ms = "Missiles: {}".format(self.missileCount)
  376. pw = "Password: {}".format(self.password)
  377. return "\n".join([ic, mt, et, zb, kb, rs, sw, sl, dr, ms, pw])
  378. def randomize(self):
  379. """ The randomizer!
  380. """
  381. # Items
  382. if random.randint(0,1) == 1:
  383. self.toggleItem("Maru Mari")
  384. if random.randint(0,1) == 1:
  385. self.toggleItem("Bombs")
  386. if random.randint(0,1) == 1:
  387. self.toggleItem("Varia")
  388. if random.randint(0,1) == 1:
  389. self.toggleItem("High Jump Boots")
  390. if random.randint(0,1) == 1:
  391. self.toggleItem("Screw Attack")
  392. if random.randint(0,1) == 1:
  393. self.toggleItem("Long Beam")
  394. beam = random.randint(0,2)
  395. if beam == 1:
  396. self.toggleItem("Ice Beam")
  397. elif beam == 2:
  398. self.toggleItem("Wave Beam")
  399. # Missile Tanks
  400. for i in range(21):
  401. if random.randint(0,1) == 1:
  402. self.toggleMissileTank(i+1)
  403. # Energy Tanks
  404. for i in range(8):
  405. if random.randint(0,1) == 1:
  406. self.toggleEnergyTank(i+1)
  407. # Zebetites
  408. for i in range(5):
  409. if random.randint(0,1) == 1:
  410. self.toggleZebetite(i+1)
  411. # Bosses killed
  412. if random.randint(0,1) == 1:
  413. self.toggleKraid()
  414. if random.randint(0,1) == 1:
  415. self.toggleRidley()
  416. if random.randint(0,1) == 1:
  417. self.toggleMotherBrain()
  418. # Statues raised
  419. if not self.kraidKilled and random.randint(0,1) == 1:
  420. self.toggleKraidStatue()
  421. if not self.ridleyKilled and random.randint(0,1) == 1:
  422. self.toggleRidleyStatue()
  423. # Doors
  424. # Brinstar 5, Norfair 4, Kraid 5, Ridley 2, Tourian 3
  425. for i in range(5):
  426. if random.randint(0,1) == 1:
  427. self.doors["Brinstar"][i+1] = True
  428. for i in range(4):
  429. if random.randint(0,1) == 1:
  430. self.doors["Norfair"][i+1] = True
  431. for i in range(5):
  432. if random.randint(0,1) == 1:
  433. self.doors["Kraid"][i+1] = True
  434. for i in range(2):
  435. if random.randint(0,1) == 1:
  436. self.doors["Ridley"][i+1] = True
  437. for i in range(3):
  438. if random.randint(0,1) == 1:
  439. self.doors["Tourian"][i+1] = True
  440. # Swimsuit
  441. # Samus has a 1/3 chance of spawning in her swimsuit.
  442. # There's no technical reason for this, just a personal choice.
  443. if random.randint(0,2) == 2:
  444. self.toggleSwimsuit()
  445. # Start Location
  446. self.startLocation = random.randint(0,4)
  447. self.missileCount = random.randint(0,255)
  448. def createBitfield(self):
  449. """ Create the 144-bit field from the game state
  450. that will generate the password.
  451. """
  452. self.initializeBitfield()
  453. # Doing this in order, which is dumb and tedious but accurate.
  454. if self.itemsCollected["Maru Mari"]:
  455. self.bitfield[0] = 1
  456. if self.missileTanks[1]:
  457. self.bitfield[1] = 1
  458. if self.doors["Brinstar"][1]:
  459. self.bitfield[2] = 1
  460. if self.doors["Brinstar"][2]:
  461. self.bitfield[3] = 1
  462. if self.energyTanks[1]:
  463. self.bitfield[4] = 1
  464. if self.doors["Brinstar"][3]:
  465. self.bitfield[5] = 1
  466. if self.itemsCollected["Bombs"]:
  467. self.bitfield[6] = 1
  468. if self.doors["Brinstar"][4]:
  469. self.bitfield[7] = 1
  470. if self.missileTanks[2]:
  471. self.bitfield[8] = 1
  472. if self.energyTanks[2]:
  473. self.bitfield[9] = 1
  474. if self.doors["Brinstar"][5]:
  475. self.bitfield[10] = 1
  476. if self.itemsCollected["Varia"]:
  477. self.bitfield[11] = 1
  478. if self.energyTanks[3]:
  479. self.bitfield[12] = 1
  480. if self.missileTanks[3]:
  481. self.bitfield[13] = 1
  482. if self.missileTanks[4]:
  483. self.bitfield[14] = 1
  484. if self.doors["Norfair"][1]:
  485. self.bitfield[15] = 1
  486. if self.missileTanks[5]:
  487. self.bitfield[16] = 1
  488. if self.missileTanks[6]:
  489. self.bitfield[17] = 1
  490. if self.missileTanks[7]:
  491. self.bitfield[18] = 1
  492. if self.missileTanks[8]:
  493. self.bitfield[19] = 1
  494. if self.missileTanks[9]:
  495. self.bitfield[20] = 1
  496. if self.missileTanks[10]:
  497. self.bitfield[21] = 1
  498. if self.missileTanks[11]:
  499. self.bitfield[22] = 1
  500. if self.doors["Norfair"][2]:
  501. self.bitfield[23] = 1
  502. if self.itemsCollected["High Jump Boots"]:
  503. self.bitfield[24] = 1
  504. if self.doors["Norfair"][3]:
  505. self.bitfield[25] = 1
  506. if self.itemsCollected["Screw Attack"]:
  507. self.bitfield[26] = 1
  508. if self.missileTanks[12]:
  509. self.bitfield[27] = 1
  510. if self.missileTanks[13]:
  511. self.bitfield[28] = 1
  512. if self.doors["Norfair"][4]:
  513. self.bitfield[29] = 1
  514. if self.energyTanks[4]:
  515. self.bitfield[30] = 1
  516. if self.missileTanks[14]:
  517. self.bitfield[31] = 1
  518. if self.doors["Kraid"][1]:
  519. self.bitfield[32] = 1
  520. if self.missileTanks[15]:
  521. self.bitfield[33] = 1
  522. if self.missileTanks[16]:
  523. self.bitfield[34] = 1
  524. if self.doors["Kraid"][2]:
  525. self.bitfield[35] = 1
  526. if self.energyTanks[5]:
  527. self.bitfield[36] = 1
  528. if self.doors["Kraid"][3]:
  529. self.bitfield[37] = 1
  530. if self.doors["Kraid"][4]:
  531. self.bitfield[38] = 1
  532. if self.missileTanks[17]:
  533. self.bitfield[39] = 1
  534. if self.missileTanks[18]:
  535. self.bitfield[40] = 1
  536. if self.doors["Kraid"][5]:
  537. self.bitfield[41] = 1
  538. if self.energyTanks[6]:
  539. self.bitfield[42] = 1
  540. if self.missileTanks[19]:
  541. self.bitfield[43] = 1
  542. if self.doors["Ridley"][1]:
  543. self.bitfield[44] = 1
  544. if self.energyTanks[7]:
  545. self.bitfield[45] = 1
  546. if self.missileTanks[20]:
  547. self.bitfield[46] = 1
  548. if self.doors["Ridley"][2]:
  549. self.bitfield[47] = 1
  550. if self.energyTanks[8]:
  551. self.bitfield[48] = 1
  552. if self.missileTanks[21]:
  553. self.bitfield[49] = 1
  554. if self.doors["Tourian"][1]:
  555. self.bitfield[50] = 1
  556. if self.doors["Tourian"][2]:
  557. self.bitfield[51] = 1
  558. if self.doors["Tourian"][3]:
  559. self.bitfield[52] = 1
  560. if self.zebetitesDestroyed[1]:
  561. self.bitfield[53] = 1
  562. if self.zebetitesDestroyed[2]:
  563. self.bitfield[54] = 1
  564. if self.zebetitesDestroyed[3]:
  565. self.bitfield[55] = 1
  566. if self.zebetitesDestroyed[4]:
  567. self.bitfield[56] = 1
  568. if self.zebetitesDestroyed[5]:
  569. self.bitfield[57] = 1
  570. if self.motherBrainKilled:
  571. self.bitfield[58] = 1
  572. # 59-63 unknown
  573. # not 64, 65, or 66 = Brinstar
  574. # 64 = Norfair
  575. # 65 and not 66 = Kraid's Lair
  576. # 66 and not 65 = Ridley's Lair
  577. # 65 and 66 = Tourian
  578. if self.startLocation == 1:
  579. self.bitfield[64] = 1
  580. if self.startLocation == 2 or self.startLocation == 4:
  581. self.bitfield[65] = 1
  582. if self.startLocation == 3 or self.startLocation == 4:
  583. self.bitfield[66] = 1
  584. # 67 is the reset bit, I want all passwords to be valid
  585. # if self.:
  586. # self.bitfield[67] = 1
  587. # 68-70 are unknown
  588. if self.swimsuit:
  589. self.bitfield[71] = 1
  590. if self.samusHas["Bombs"]:
  591. self.bitfield[72] = 1
  592. if self.samusHas["High Jump Boots"]:
  593. self.bitfield[73] = 1
  594. if self.samusHas["Long Beam"]:
  595. self.bitfield[74] = 1
  596. if self.samusHas["Screw Attack"]:
  597. self.bitfield[75] = 1
  598. if self.samusHas["Maru Mari"]:
  599. self.bitfield[76] = 1
  600. if self.samusHas["Varia"]:
  601. self.bitfield[77] = 1
  602. if self.samusHas["Wave Beam"]:
  603. self.bitfield[78] = 1
  604. if self.samusHas["Ice Beam"]:
  605. self.bitfield[79] = 1
  606. # Missile count
  607. # +2^n from 0 to 7
  608. binMissiles = bin(self.missileCount).replace('0b','')[::-1]
  609. while len(binMissiles) < 8:
  610. binMissiles += "0"
  611. if int(binMissiles[0]) == 1:
  612. self.bitfield[80] = 1
  613. if int(binMissiles[1]) == 1:
  614. self.bitfield[81] = 1
  615. if int(binMissiles[2]) == 1:
  616. self.bitfield[82] = 1
  617. if int(binMissiles[3]) == 1:
  618. self.bitfield[83] = 1
  619. if int(binMissiles[4]) == 1:
  620. self.bitfield[84] = 1
  621. if int(binMissiles[5]) == 1:
  622. self.bitfield[85] = 1
  623. if int(binMissiles[6]) == 1:
  624. self.bitfield[86] = 1
  625. if int(binMissiles[7]) == 1:
  626. self.bitfield[87] = 1
  627. # 88-119 are game age, let's randomize
  628. if random.randint(0,1) == 1:
  629. self.bitfield[88] = 1
  630. if random.randint(0,1) == 1:
  631. self.bitfield[89] = 1
  632. if random.randint(0,1) == 1:
  633. self.bitfield[90] = 1
  634. if random.randint(0,1) == 1:
  635. self.bitfield[91] = 1
  636. if random.randint(0,1) == 1:
  637. self.bitfield[92] = 1
  638. if random.randint(0,1) == 1:
  639. self.bitfield[93] = 1
  640. if random.randint(0,1) == 1:
  641. self.bitfield[94] = 1
  642. if random.randint(0,1) == 1:
  643. self.bitfield[95] = 1
  644. if random.randint(0,1) == 1:
  645. self.bitfield[96] = 1
  646. if random.randint(0,1) == 1:
  647. self.bitfield[97] = 1
  648. if random.randint(0,1) == 1:
  649. self.bitfield[98] = 1
  650. if random.randint(0,1) == 1:
  651. self.bitfield[99] = 1
  652. if random.randint(0,1) == 1:
  653. self.bitfield[100] = 1
  654. if random.randint(0,1) == 1:
  655. self.bitfield[101] = 1
  656. if random.randint(0,1) == 1:
  657. self.bitfield[102] = 1
  658. if random.randint(0,1) == 1:
  659. self.bitfield[103] = 1
  660. if random.randint(0,1) == 1:
  661. self.bitfield[104] = 1
  662. if random.randint(0,1) == 1:
  663. self.bitfield[105] = 1
  664. if random.randint(0,1) == 1:
  665. self.bitfield[106] = 1
  666. if random.randint(0,1) == 1:
  667. self.bitfield[107] = 1
  668. if random.randint(0,1) == 1:
  669. self.bitfield[108] = 1
  670. if random.randint(0,1) == 1:
  671. self.bitfield[109] = 1
  672. if random.randint(0,1) == 1:
  673. self.bitfield[110] = 1
  674. if random.randint(0,1) == 1:
  675. self.bitfield[111] = 1
  676. if random.randint(0,1) == 1:
  677. self.bitfield[112] = 1
  678. if random.randint(0,1) == 1:
  679. self.bitfield[113] = 1
  680. if random.randint(0,1) == 1:
  681. self.bitfield[114] = 1
  682. if random.randint(0,1) == 1:
  683. self.bitfield[115] = 1
  684. if random.randint(0,1) == 1:
  685. self.bitfield[116] = 1
  686. if random.randint(0,1) == 1:
  687. self.bitfield[117] = 1
  688. if random.randint(0,1) == 1:
  689. self.bitfield[118] = 1
  690. if random.randint(0,1) == 1:
  691. self.bitfield[119] = 1
  692. # 120-123 are unknown
  693. # I have no idea why these are at the end. I wonder if Kraid and
  694. # Ridley were relatively late additions to the game? (Or maybe
  695. # they were just implemented late in the game's development.)
  696. if self.ridleyKilled:
  697. self.bitfield[124] = 1
  698. if self.ridleyStatue:
  699. self.bitfield[125] = 1
  700. if self.kraidKilled:
  701. self.bitfield[126] = 1
  702. if self.kraidStatue:
  703. self.bitfield[127] = 1
  704. def generatePassword(self):
  705. """ Generate the password from the bitfield.
  706. This is a five-step process.
  707. 1) Reverse the order of each 8-bit byte to make it little-endian.
  708. 2) Cycle the entire bitfield 0-7 bits to the right.
  709. Append the number of shifts in binary to the end - again, little-endian.
  710. 3) Create the checksum by turning each byte into a decimal number,
  711. summing them, converting the sum *back* to binary, and taking the lowest
  712. 8 bits of that binary sum and adding it - BIG-ENDIAN - to the end.
  713. 4) Separate the bitstream into ***6-bit*** chunks and create a decimal
  714. number from each chunk (0-63).
  715. 5) Associate each decimal number with a letter from the Metroid Alphabet
  716. (listed at the top of __init__()).
  717. I'm not doing step 2 yet, which is equivalent to shifting 0 places
  718. and making the shift byte 00000000.
  719. """
  720. # not gonna do the bit-shifting yet
  721. bitfield = self.bitfield
  722. bitfield = bitfield + [0,0,0,0,0,0,0,0] # add the zero shift byte
  723. self.fullbitfield = "".join([str(x) for x in bitfield])
  724. newBitfield = []
  725. for i in range(17):
  726. j = i * 8
  727. k = j + 8
  728. word = self.fullbitfield[j:k][::-1] # I thought [j:k:-1] should work but it doesn't
  729. newBitfield.append(word)
  730. decChecksum = sum([int(x, 2) for x in newBitfield])
  731. bitfield = "".join(newBitfield)
  732. binChecksum = bin(decChecksum).replace('0b','')
  733. checksum = binChecksum[-8:]
  734. while len(checksum) < 8:
  735. checksum = "0" + checksum
  736. for bit in checksum:
  737. bitfield += bit
  738. letters = []
  739. letter = []
  740. for bit in bitfield:
  741. letter.append(bit)
  742. if len(letter) == 6:
  743. letters.append(self.alphabet[int("".join(letter),2)])
  744. letter = []
  745. words = []
  746. word = []
  747. for lt in letters:
  748. word.append(lt)
  749. if len(word) == 6:
  750. words.append("".join(word))
  751. word = []
  752. words.append("".join(word))
  753. self.password = " ".join(words)
  754. return self.password
  755. def decodePassword(self, pwd):
  756. """ Sanity checking! This function decodes an input password back into a bitfield,
  757. so that you can check that it was properly encoded.
  758. Q: Why doesn't this display the game state?
  759. A: I trust that https://www.truepeacein.space properly encodes the game state.
  760. So when I create a game state with the randomizer, I can recreate that
  761. game state at TPIS and use the password generates as its input, to check
  762. against my randomized game password. In other words, this is a testing
  763. function, and in the intended use case I'll know what the input bitfield is
  764. and be able to check against it.
  765. """
  766. densePwd = pwd.replace(" ","")
  767. numPwd = []
  768. for chr in densePwd:
  769. numPwd.append(self.alphabet.index(chr))
  770. bitPwd = [bin(x).replace("0b","") for x in numPwd]
  771. longBitPwd = []
  772. for word in bitPwd:
  773. longword = word
  774. while len(longword) < 6:
  775. longword = "0" + longword
  776. longBitPwd.append(longword)
  777. newBitfield = "".join(longBitPwd)
  778. csm = sum([int(x) for x in newBitfield[:136]])
  779. print(csm)
  780. for i in range(len(newBitfield)):
  781. print(newBitfield[i], end="")
  782. if i%8 == 7:
  783. print(" ", end="")
  784. if i%64 == 63:
  785. print()
  786. class MetroidPoster(PineappleBot):
  787. @hourly(minute=50)
  788. def postPassword(self):
  789. gs = MetroidState()
  790. gs.initializeBitfield()
  791. gs.randomize()
  792. gs.createBitfield()
  793. gs.generatePassword()
  794. post_text = gs.toString()
  795. self.mastodon.status_post(post_text, visibility = "unlisted", spoiler_text = "Metroid password: {}".format(gs.password))
  796. print("Metroidgen scheduled: posted {}".format(gs.password))