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.

metroidgen.py 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  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 randomize(self):
  378. """ The randomizer!
  379. """
  380. # Items
  381. if random.randint(0,1) == 1:
  382. self.toggleItem("Maru Mari")
  383. if random.randint(0,1) == 1:
  384. self.toggleItem("Bombs")
  385. if random.randint(0,1) == 1:
  386. self.toggleItem("Varia")
  387. if random.randint(0,1) == 1:
  388. self.toggleItem("High Jump Boots")
  389. if random.randint(0,1) == 1:
  390. self.toggleItem("Screw Attack")
  391. if random.randint(0,1) == 1:
  392. self.toggleItem("Long Beam")
  393. beam = random.randint(0,2)
  394. if beam == 1:
  395. self.toggleItem("Ice Beam")
  396. elif beam == 2:
  397. self.toggleItem("Wave Beam")
  398. # Missile Tanks
  399. for i in range(21):
  400. if random.randint(0,1) == 1:
  401. self.toggleMissileTank(i+1)
  402. # Energy Tanks
  403. for i in range(8):
  404. if random.randint(0,1) == 1:
  405. self.toggleEnergyTank(i+1)
  406. # Zebetites
  407. for i in range(5):
  408. if random.randint(0,1) == 1:
  409. self.toggleZebetite(i+1)
  410. # Bosses killed
  411. if random.randint(0,1) == 1:
  412. self.toggleKraid()
  413. if random.randint(0,1) == 1:
  414. self.toggleRidley()
  415. if random.randint(0,1) == 1:
  416. self.toggleMotherBrain()
  417. # Statues raised
  418. if not self.kraidKilled and random.randint(0,1) == 1:
  419. self.toggleKraidStatue()
  420. if not self.ridleyKilled and random.randint(0,1) == 1:
  421. self.toggleRidleyStatue()
  422. # Doors
  423. # Brinstar 5, Norfair 4, Kraid 5, Ridley 2, Tourian 3
  424. for i in range(5):
  425. if random.randint(0,1) == 1:
  426. self.doors["Brinstar"][i+1] = True
  427. for i in range(4):
  428. if random.randint(0,1) == 1:
  429. self.doors["Norfair"][i+1] = True
  430. for i in range(5):
  431. if random.randint(0,1) == 1:
  432. self.doors["Kraid"][i+1] = True
  433. for i in range(2):
  434. if random.randint(0,1) == 1:
  435. self.doors["Ridley"][i+1] = True
  436. for i in range(3):
  437. if random.randint(0,1) == 1:
  438. self.doors["Tourian"][i+1] = True
  439. # Swimsuit
  440. # Samus has a 1/3 chance of spawning in her swimsuit.
  441. # There's no technical reason for this, just a personal choice.
  442. if random.randint(0,2) == 2:
  443. self.toggleSwimsuit()
  444. # Start Location
  445. self.startLocation = random.randint(0,4)
  446. self.missileCount = random.randint(0,255)
  447. def createBitfield(self):
  448. """ Create the 144-bit field from the game state
  449. that will generate the password.
  450. """
  451. self.initializeBitfield()
  452. # Doing this in order, which is dumb and tedious but accurate.
  453. if self.itemsCollected["Maru Mari"]:
  454. self.bitfield[0] = 1
  455. if self.missileTanks[1]:
  456. self.bitfield[1] = 1
  457. if self.doors["Brinstar"][1]:
  458. self.bitfield[2] = 1
  459. if self.doors["Brinstar"][2]:
  460. self.bitfield[3] = 1
  461. if self.energyTanks[1]:
  462. self.bitfield[4] = 1
  463. if self.doors["Brinstar"][3]:
  464. self.bitfield[5] = 1
  465. if self.itemsCollected["Bombs"]:
  466. self.bitfield[6] = 1
  467. if self.doors["Brinstar"][4]:
  468. self.bitfield[7] = 1
  469. if self.missileTanks[2]:
  470. self.bitfield[8] = 1
  471. if self.energyTanks[2]:
  472. self.bitfield[9] = 1
  473. if self.doors["Brinstar"][5]:
  474. self.bitfield[10] = 1
  475. if self.itemsCollected["Varia"]:
  476. self.bitfield[11] = 1
  477. if self.energyTanks[3]:
  478. self.bitfield[12] = 1
  479. if self.missileTanks[3]:
  480. self.bitfield[13] = 1
  481. if self.missileTanks[4]:
  482. self.bitfield[14] = 1
  483. if self.doors["Norfair"][1]:
  484. self.bitfield[15] = 1
  485. if self.missileTanks[5]:
  486. self.bitfield[16] = 1
  487. if self.missileTanks[6]:
  488. self.bitfield[17] = 1
  489. if self.missileTanks[7]:
  490. self.bitfield[18] = 1
  491. if self.missileTanks[8]:
  492. self.bitfield[19] = 1
  493. if self.missileTanks[9]:
  494. self.bitfield[20] = 1
  495. if self.missileTanks[10]:
  496. self.bitfield[21] = 1
  497. if self.missileTanks[11]:
  498. self.bitfield[22] = 1
  499. if self.doors["Norfair"][2]:
  500. self.bitfield[23] = 1
  501. if self.itemsCollected["High Jump Boots"]:
  502. self.bitfield[24] = 1
  503. if self.doors["Norfair"][3]:
  504. self.bitfield[25] = 1
  505. if self.itemsCollected["Screw Attack"]:
  506. self.bitfield[26] = 1
  507. if self.missileTanks[12]:
  508. self.bitfield[27] = 1
  509. if self.missileTanks[13]:
  510. self.bitfield[28] = 1
  511. if self.doors["Norfair"][4]:
  512. self.bitfield[29] = 1
  513. if self.energyTanks[4]:
  514. self.bitfield[30] = 1
  515. if self.missileTanks[14]:
  516. self.bitfield[31] = 1
  517. if self.doors["Kraid"][1]:
  518. self.bitfield[32] = 1
  519. if self.missileTanks[15]:
  520. self.bitfield[33] = 1
  521. if self.missileTanks[16]:
  522. self.bitfield[34] = 1
  523. if self.doors["Kraid"][2]:
  524. self.bitfield[35] = 1
  525. if self.energyTanks[5]:
  526. self.bitfield[36] = 1
  527. if self.doors["Kraid"][3]:
  528. self.bitfield[37] = 1
  529. if self.doors["Kraid"][4]:
  530. self.bitfield[38] = 1
  531. if self.missileTanks[17]:
  532. self.bitfield[39] = 1
  533. if self.missileTanks[18]:
  534. self.bitfield[40] = 1
  535. if self.doors["Kraid"][5]:
  536. self.bitfield[41] = 1
  537. if self.energyTanks[6]:
  538. self.bitfield[42] = 1
  539. if self.missileTanks[19]:
  540. self.bitfield[43] = 1
  541. if self.doors["Ridley"][1]:
  542. self.bitfield[44] = 1
  543. if self.energyTanks[7]:
  544. self.bitfield[45] = 1
  545. if self.missileTanks[20]:
  546. self.bitfield[46] = 1
  547. if self.doors["Ridley"][2]:
  548. self.bitfield[47] = 1
  549. if self.energyTanks[8]:
  550. self.bitfield[48] = 1
  551. if self.missileTanks[21]:
  552. self.bitfield[49] = 1
  553. if self.doors["Tourian"][1]:
  554. self.bitfield[50] = 1
  555. if self.doors["Tourian"][2]:
  556. self.bitfield[51] = 1
  557. if self.doors["Tourian"][3]:
  558. self.bitfield[52] = 1
  559. if self.zebetitesDestroyed[1]:
  560. self.bitfield[53] = 1
  561. if self.zebetitesDestroyed[2]:
  562. self.bitfield[54] = 1
  563. if self.zebetitesDestroyed[3]:
  564. self.bitfield[55] = 1
  565. if self.zebetitesDestroyed[4]:
  566. self.bitfield[56] = 1
  567. if self.zebetitesDestroyed[5]:
  568. self.bitfield[57] = 1
  569. if self.motherBrainKilled:
  570. self.bitfield[58] = 1
  571. # 59-63 unknown
  572. # not 64, 65, or 66 = Brinstar
  573. # 64 = Norfair
  574. # 65 and not 66 = Kraid's Lair
  575. # 66 and not 65 = Ridley's Lair
  576. # 65 and 66 = Tourian
  577. if self.startLocation == 1:
  578. self.bitfield[64] = 1
  579. if self.startLocation == 2 or self.startLocation == 4:
  580. self.bitfield[65] = 1
  581. if self.startLocation == 3 or self.startLocation == 4:
  582. self.bitfield[66] = 1
  583. # 67 is the reset bit, I want all passwords to be valid
  584. # if self.:
  585. # self.bitfield[67] = 1
  586. # 68-70 are unknown
  587. if self.swimsuit:
  588. self.bitfield[71] = 1
  589. if self.samusHas["Bombs"]:
  590. self.bitfield[72] = 1
  591. if self.samusHas["High Jump Boots"]:
  592. self.bitfield[73] = 1
  593. if self.samusHas["Long Beam"]:
  594. self.bitfield[74] = 1
  595. if self.samusHas["Screw Attack"]:
  596. self.bitfield[75] = 1
  597. if self.samusHas["Maru Mari"]:
  598. self.bitfield[76] = 1
  599. if self.samusHas["Varia"]:
  600. self.bitfield[77] = 1
  601. if self.samusHas["Wave Beam"]:
  602. self.bitfield[78] = 1
  603. if self.samusHas["Ice Beam"]:
  604. self.bitfield[79] = 1
  605. # Missile count
  606. # +2^n from 0 to 7
  607. binMissiles = bin(self.missileCount).replace('0b','')[::-1]
  608. while len(binMissiles) < 8:
  609. binMissiles += "0"
  610. if int(binMissiles[0]) == 1:
  611. self.bitfield[80] = 1
  612. if int(binMissiles[1]) == 1:
  613. self.bitfield[81] = 1
  614. if int(binMissiles[2]) == 1:
  615. self.bitfield[82] = 1
  616. if int(binMissiles[3]) == 1:
  617. self.bitfield[83] = 1
  618. if int(binMissiles[4]) == 1:
  619. self.bitfield[84] = 1
  620. if int(binMissiles[5]) == 1:
  621. self.bitfield[85] = 1
  622. if int(binMissiles[6]) == 1:
  623. self.bitfield[86] = 1
  624. if int(binMissiles[7]) == 1:
  625. self.bitfield[87] = 1
  626. # 88-119 are game age, leaving at 0
  627. # 120-123 are unknown
  628. # I have no idea why these are at the end. I wonder if Kraid and
  629. # Ridley were relatively late additions to the game? (Or maybe
  630. # they were just implemented late in the game's development.)
  631. if self.ridleyKilled:
  632. self.bitfield[124] = 1
  633. if self.ridleyStatue:
  634. self.bitfield[125] = 1
  635. if self.kraidKilled:
  636. self.bitfield[126] = 1
  637. if self.kraidStatue:
  638. self.bitfield[127] = 1
  639. def generatePassword(self):
  640. """ Generate the password from the bitfield.
  641. This is a five-step process.
  642. 1) Reverse the order of each 8-bit byte to make it little-endian.
  643. 2) Cycle the entire bitfield 0-7 bits to the right.
  644. Append the number of shifts in binary to the end - again, little-endian.
  645. 3) Create the checksum by turning each byte into a decimal number,
  646. summing them, converting the sum *back* to binary, and taking the lowest
  647. 8 bits of that binary sum and adding it - BIG-ENDIAN - to the end.
  648. 4) Separate the bitstream into ***6-bit*** chunks and create a decimal
  649. number from each chunk (0-63).
  650. 5) Associate each decimal number with a letter from the Metroid Alphabet
  651. (listed at the top of __init__()).
  652. I'm not doing step 2 yet, which is equivalent to shifting 0 places
  653. and making the shift byte 00000000.
  654. """
  655. # not gonna do the bit-shifting yet
  656. bitfield = self.bitfield
  657. bitfield = bitfield + [0,0,0,0,0,0,0,0] # add the zero shift byte
  658. self.fullbitfield = "".join([str(x) for x in bitfield])
  659. newBitfield = []
  660. for i in range(17):
  661. j = i * 8
  662. k = j + 8
  663. word = self.fullbitfield[j:k][::-1] # I thought [j:k:-1] should work but it doesn't
  664. newBitfield.append(word)
  665. decChecksum = sum([int(x, 2) for x in newBitfield])
  666. bitfield = "".join(newBitfield)
  667. binChecksum = bin(decChecksum).replace('0b','')
  668. checksum = binChecksum[-8:]
  669. while len(checksum) < 8:
  670. checksum = "0" + checksum
  671. for bit in checksum:
  672. bitfield += bit
  673. letters = []
  674. letter = []
  675. for bit in bitfield:
  676. letter.append(bit)
  677. if len(letter) == 6:
  678. letters.append(self.alphabet[int("".join(letter),2)])
  679. letter = []
  680. words = []
  681. word = []
  682. for lt in letters:
  683. word.append(lt)
  684. if len(word) == 6:
  685. words.append("".join(word))
  686. word = []
  687. words.append("".join(word))
  688. self.password = " ".join(words)
  689. return self.password
  690. def decodePassword(self, pwd):
  691. """ Sanity checking! This function decodes an input password back into a bitfield,
  692. so that you can check that it was properly encoded.
  693. """
  694. densePwd = pwd.replace(" ","")
  695. numPwd = []
  696. for chr in densePwd:
  697. numPwd.append(self.alphabet.index(chr))
  698. bitPwd = [bin(x).replace("0b","") for x in numPwd]
  699. longBitPwd = []
  700. for word in bitPwd:
  701. longword = word
  702. while len(longword) < 6:
  703. longword = "0" + longword
  704. longBitPwd.append(longword)
  705. newBitfield = "".join(longBitPwd)
  706. csm = sum([int(x) for x in newBitfield[:136]])
  707. print(csm)
  708. for i in range(len(newBitfield)):
  709. print(newBitfield[i], end="")
  710. if i%8 == 7:
  711. print(" ", end="")
  712. if i%64 == 63:
  713. print()
  714. def main():
  715. gs = MetroidState()
  716. gs.randomize()
  717. gs.createBitfield()
  718. gs.generatePassword()
  719. print(gs.toString())
  720. if __name__ == "__main__":
  721. if len(sys.argv) == 2:
  722. gs = MetroidState()
  723. gs.decodePassword(sys.argv[1])
  724. else:
  725. main()