A save state/password generator for the original Metroid.
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

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