fork(1) download
  1. """
  2. Author: mbomb007
  3. Game: Atomas
  4. Last Edited: Nov. 29, 2017
  5.  
  6. Directions:
  7. 1. Copy code
  8. 2. Paste at: repl.it/languages/Python
  9. 3. Uncomment main() at the bottom
  10. 4. Remove any testing code below the call to main()
  11. 5. Run to play
  12. 6. Notify me in chat if you find a bug: chat.stackexchange.com/users/138114/
  13. """
  14.  
  15. from random import choice, randint
  16.  
  17. # TODO: Switch back to using a recursive reaction_check()
  18. # TODO?: Change to Object-Oriented for Atoms
  19. # TODO: display help, options
  20. # - option for circle print
  21. # - option for showing all collapse steps
  22. # - option for index confirm?
  23.  
  24. def gcd(a, b):
  25. while b: a, b = b, a % b
  26. return a
  27. def _lcm(a, b): return a * b // gcd(a, b)
  28. def lcm(*args): return reduce(_lcm, args)
  29.  
  30. # constants
  31. START_ATOMS = 6
  32. LIMIT = 18
  33. SPAWN_RANGE = 6
  34. CCW = -1 # which direction in the list is counter-clockwise (-1, 1)?
  35.  
  36. # vars
  37. L = []
  38. turn = 0
  39. highest = 3
  40.  
  41. # specials
  42. MIN_ELEMENT = 1 # used to check whether element or special
  43. PLUS = 0
  44. MINUS = -1
  45. DARK_PLUS = -2
  46. NEUTRINO = -3
  47.  
  48. SPECIALS = [PLUS, MINUS, DARK_PLUS, NEUTRINO]
  49.  
  50. SPECIAL_STRING = {
  51. PLUS : '+',
  52. MINUS : '-',
  53. DARK_PLUS : 'x',
  54. NEUTRINO : '?'
  55. }
  56.  
  57.  
  58. # chance to spawn every turn
  59. SPAWN_RATE = {
  60. PLUS : 5,
  61. MINUS : 20,
  62. DARK_PLUS : 90,
  63. NEUTRINO : 60
  64. }
  65. # guaranteed to spawn at least once every X turns
  66. MAX_WAIT = SPAWN_RATE
  67.  
  68. # only appear after this score
  69. SPAWN_WALL = {
  70. PLUS : 0,
  71. MINUS : 0,
  72. DARK_PLUS : 750,
  73. NEUTRINO : 1500
  74. }
  75.  
  76.  
  77. # 180
  78. ODDS_TOTAL = lcm(*SPAWN_RATE.values())
  79. # [36, 9, 2, 3] ->
  80. # [36, 45, 47, 50] for IF comparisons
  81. SPECIAL_ODDS = []
  82. for i in xrange(len(SPAWN_RATE)):
  83. SPECIAL_ODDS.append(ODDS_TOTAL // SPAWN_RATE[ SPECIALS[i] ] + (SPECIAL_ODDS[i-1] if i > 0 else 0))
  84.  
  85.  
  86. INC_PLUS = 1
  87. INC_DARK = 3
  88.  
  89. last_plus = 1
  90. last_minus = 1
  91. last_dark = 1
  92. last_neutrino = 1
  93.  
  94. # scoring
  95. score_atom = 1
  96. score_value = 0
  97.  
  98. PLUS_VALUE = 1
  99.  
  100.  
  101. ################################################################################
  102.  
  103.  
  104. def atom_to_string(atom):
  105. if atom >= MIN_ELEMENT:
  106. return str(atom)
  107. else:
  108. return SPECIAL_STRING[atom]
  109.  
  110.  
  111. def get_value(atom):
  112. if atom >= MIN_ELEMENT:
  113. return atom
  114. else:
  115. return PLUS_VALUE
  116.  
  117.  
  118. # TODO?
  119. def pretty_print(board, tab = 0):
  120. print "\t"*tab + " ".join(map(atom_to_string, board))
  121.  
  122.  
  123. def score_simple(a):
  124. return int(round( 1.5 * a + 1.25 ))
  125.  
  126.  
  127. # http://a...content-available-to-author-only...a.com/wiki/Score
  128. # middle, outer, number of chain reaction
  129. def score_chain(m, o, r):
  130. if o < m - 1: o = m - 1
  131. return ( -m + o + 3 ) * r - m + 3 * o + 3
  132.  
  133.  
  134. def can_react(idx):
  135. """Return whether a reaction can occur at a specific index"""
  136. if len(L) < 3:
  137. return False
  138. if L[idx] == PLUS:
  139. return L[(idx-1)%len(L)] >= MIN_ELEMENT and L[(idx-1)%len(L)] == L[(idx+1)%len(L)]
  140. elif L[idx] == DARK_PLUS:
  141. return True
  142. else:
  143. return False
  144.  
  145.  
  146. def reaction(idx, chain = -1, dark = False):
  147. """Combine atoms at a specific index"""
  148. global L, score_value
  149.  
  150. l_idx = (idx-1)%len(L)
  151. r_idx = (idx+1)%len(L)
  152. left = L[l_idx]
  153. right = L[r_idx]
  154.  
  155. if len(L) > 2 and (dark or (left >= MIN_ELEMENT and left == right)):
  156.  
  157. chain += 1
  158.  
  159. if chain == -1:
  160. # print intermediate
  161. pretty_print(L, 1)
  162.  
  163. # first time through?
  164. if chain < 1:
  165. a = get_value(max(left, right))
  166.  
  167. # score the simple reaction
  168. score_value += score_simple(a)
  169.  
  170. # it's possible for a DARK_PLUS to combine with PLUS atoms
  171. L[idx] = a + (INC_DARK if dark else INC_PLUS)
  172.  
  173. else:
  174. # score the chain reaction
  175. score_value += score_chain(L[idx], left, chain)
  176.  
  177. # assume both left and right atoms are equal, since dark plus doesn't affect
  178. L[idx] = max(L[idx], left) + INC_PLUS + (L[idx] == left)
  179.  
  180. old_len = len(L)
  181.  
  182. # remove later atoms first
  183. if l_idx > r_idx:
  184. l_idx, r_idx = r_idx, l_idx
  185. del L[r_idx]
  186. del L[l_idx]
  187.  
  188. # shift index because atoms were removed
  189. # if idx was 0, both were after, so don't shift
  190. # if idx was len(L)-1, then both atoms removed were in front, so subtract 2.
  191. if idx == 0:
  192. pass
  193. elif idx == old_len-1:
  194. idx -= 2
  195. else:
  196. idx -= 1
  197.  
  198. # recurse. dark plus only helps on the first time
  199. return reaction(idx, chain)
  200.  
  201. if chain >= 0:
  202. # print after done
  203. pretty_print(L, 1)
  204.  
  205. # return the index and chain achieved, since chain could continue in another reaction
  206. return (idx, chain)
  207.  
  208.  
  209. def reaction_check(idx, chain):
  210. """Perform any reactions possible"""
  211. counter = 0
  212.  
  213. # check every location
  214. while counter < len(L):
  215. check_idx = (idx + counter * CCW) % len(L)
  216. if can_react(check_idx):
  217. old_chain = chain
  218. idx, chain = reaction(check_idx, chain)
  219. if old_chain != chain:
  220. break
  221. counter += 1
  222.  
  223. return (idx, chain)
  224.  
  225.  
  226. # TODO: Switch back to using a recursive reaction_check(), because this sucks
  227. def perform_all_reactions(index, chain):
  228. old_chain = chain
  229. index, chain = reaction_check(index, chain)
  230. while old_chain != chain:
  231. old_chain = chain
  232. index, chain = reaction_check(index, chain)
  233. return index, chain
  234.  
  235.  
  236. def get_next_atom():
  237. """Get the next atom for the user to place"""
  238. global last_plus, last_minus, last_dark, last_neutrino
  239.  
  240. # current atom
  241. atom = None
  242.  
  243. # special atom?
  244. if last_dark >= MAX_WAIT[DARK_PLUS] - 1:
  245. atom = DARK_PLUS
  246. last_dark = 1
  247. # (x) > (+)
  248. last_plus = 1
  249. elif last_neutrino >= MAX_WAIT[NEUTRINO] - 1:
  250. atom = NEUTRINO
  251. last_neutrino = 1
  252. elif last_minus >= MAX_WAIT[MINUS] - 1:
  253. atom = MINUS
  254. last_minus = 1
  255. # (-) > (+)
  256. last_plus = 1
  257. elif last_plus >= MAX_WAIT[PLUS] - 1:
  258. atom = PLUS
  259. last_plus = 1
  260. else:
  261. # choose atom randomly
  262. atom = get_rand_atom()
  263.  
  264. # increment time since last specials
  265. if atom != PLUS and score_value >= SPAWN_WALL[PLUS]:
  266. last_plus += 1
  267. if atom != MINUS and score_value >= SPAWN_WALL[MINUS]:
  268. last_minus += 1
  269. if atom != DARK_PLUS and score_value >= SPAWN_WALL[DARK_PLUS]:
  270. last_dark += 1
  271. if atom != NEUTRINO and score_value >= SPAWN_WALL[NEUTRINO]:
  272. last_neutrino += 1
  273.  
  274. return atom
  275.  
  276.  
  277. def get_rand_atom():
  278. """Get a random atom to place"""
  279. rnd = randint(0, ODDS_TOTAL - 1)
  280.  
  281. # if special atom
  282. for i in xrange(len(SPECIAL_ODDS)):
  283. if rnd < SPECIAL_ODDS[i]:
  284. if score_value >= SPAWN_WALL[ SPECIALS[i] ]:
  285. return SPECIALS[i]
  286. else:
  287. break
  288.  
  289. lowest = highest - SPAWN_RANGE
  290. # atoms within spawn range + atoms below spawn range that are in play
  291. return choice(range(max(1, lowest), highest + 1) +
  292. list({atom for atom in L if MIN_ELEMENT <= atom < lowest}))
  293.  
  294.  
  295. def user_insert_index(msg):
  296. """Get user input for index to insert an atom at"""
  297. index = None
  298. while index < -len(L) or index > len(L): # allow insert at end (same as 0)
  299. index = int(raw_input(msg))
  300. return index
  301.  
  302.  
  303. def user_atom_index(msg):
  304. """Get user input for index to access an atom at"""
  305. index = None
  306. while index < -len(L) or index > len(L)-1:
  307. index = int(raw_input(msg))
  308. return index
  309.  
  310.  
  311. def user_action(atom):
  312. """Perform user interaction"""
  313.  
  314. # display board
  315. pretty_print(L)
  316.  
  317. s = atom_to_string(atom)
  318. index = None
  319. chain = -1
  320.  
  321. # (#)
  322. if atom >= MIN_ELEMENT:
  323. msg = "(%s) Enter index to insert atom at (0-%s):" % (s, len(L)-1)
  324. index = user_insert_index(msg)
  325. L.insert(index, atom)
  326.  
  327. # (+) or (x)
  328. elif atom == PLUS or atom == DARK_PLUS:
  329. msg = "(%s) Enter index to insert atom at (0-%s):" % (s, len(L)-1)
  330. index = user_insert_index(msg)
  331. L.insert(index, atom)
  332. index, chain = reaction(index, dark=(atom == DARK_PLUS))
  333.  
  334. # (-)
  335. elif atom == MINUS:
  336. msg = "(%s) Enter index of atom to remove (0-%s):" % (s, len(L)-1)
  337. index = user_atom_index(msg)
  338.  
  339. # remove atom
  340. atom = L[index]
  341. s = atom_to_string(atom)
  342. del L[index]
  343.  
  344. # perform all possible reactions
  345. index, chain = perform_all_reactions(index, chain)
  346.  
  347. if atom != PLUS:
  348. # display board
  349. pretty_print(L)
  350.  
  351. # change to (+) ?
  352. msg = "\t(%s) Change to (%s)? Enter 1 for Yes, or 0 for No:" % (s, SPECIAL_STRING[PLUS])
  353. yes = int(raw_input(msg))
  354. if yes:
  355. return user_action(PLUS)
  356. else:
  357. return user_action(atom)
  358.  
  359. # (?)
  360. elif atom == NEUTRINO:
  361. msg = "(%s) Enter index of atom to copy (0-%s):" % (s, len(L)-1)
  362. index = user_atom_index(msg)
  363. atom = L[index]
  364. return user_action(atom)
  365.  
  366. # perform all possible reactions
  367. index, chain = perform_all_reactions(index, chain)
  368.  
  369. return (index, chain)
  370.  
  371.  
  372. def game_over():
  373. global L, score_value
  374.  
  375. # add up points from atoms in play
  376. while L:
  377. score_value += get_value(L.pop())
  378.  
  379. print """\nGAME OVER
  380. Atom: %s
  381. Score: %s
  382. Turns: %s
  383. """ % (score_atom, score_value, turn)
  384.  
  385.  
  386. def main():
  387. global L, highest, turn, score_atom, score_value
  388.  
  389. # initialize ring randomly
  390. L = []
  391. for i in xrange(START_ATOMS):
  392. L.append(choice(xrange(1, highest+1)))
  393. score_atom = max(L)
  394.  
  395. # TODO: display help, options
  396.  
  397. # game loop
  398. while len(L) < LIMIT:
  399. # get atom for user to place
  400. atom = get_next_atom()
  401.  
  402. # perform user action
  403. user_action(atom)
  404.  
  405. # update highest atom reached
  406. score_atom = max(score_atom, *L)
  407.  
  408. # next turn
  409. turn += 1
  410. if turn % 40 == 0:
  411. highest += 1
  412.  
  413. # GAME OVER
  414. game_over()
  415.  
  416. #main()
  417.  
  418.  
  419.  
  420. # TESTING
  421.  
  422. L = [7, 7, 0, 9, 0, 10, 0, 13, 0, 12, 0, 12, 0, 13, 14]
  423. perform_all_reactions(*reaction(10))
  424.  
  425.  
  426.  
Success #stdin #stdout 0.01s 11504KB
stdin
Standard input is empty
stdout
	7 7 + 9 + 10 + 13 + 13 + 13 14
	7 7 + 9 + 10 + 14 + 13 14