game_state.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import string
  2. import random
  3. from menu import new_pause_menu, new_main_menu, new_story_branch_menu
  4. from dialog import Dialog, new_how_to_play_dialog, new_about_dialog, new_game_over_dialog
  5. from data_manager import DataManager
  6. from puzzle import Puzzle, new_puzzle
  7. from word_wrap import wrap_puzzle_text
  8. class GameState:
  9. def __init__(self, data_manager, user_preferences, puzzle_dimensions):
  10. self.puzzle_dimensions = puzzle_dimensions
  11. self.user_preferences = user_preferences
  12. self.data_manager = data_manager
  13. self.mode = None
  14. self.screen = None
  15. self.dialog_list = []
  16. self.level_history = None
  17. def load_level_history(self, level_history):
  18. self.level_history = level_history
  19. def load_story_level(self, story, level_id, skip_pre_narrative):
  20. self.story = story
  21. level_data = next((level for level in story['levels'] if level['id'] == level_id), None)
  22. if level_data is None:
  23. raise ValueError(f"Level with id {level_id} not found")
  24. self.level = level_data
  25. if level_data['id'] not in self.level_history['unlocked_levels']:
  26. self.level_history['unlocked_levels'].append(self.level['id'])
  27. self.dialog_list.append(Dialog([level_data['name']], self))
  28. if not skip_pre_narrative and level_data['pre_narrative'] is not None:
  29. self.dialog_list.append(Dialog(level_data['pre_narrative'], self))
  30. if level_data['puzzle'] is not None:
  31. self.puzzle = Puzzle(level_data['puzzle']['quote'], level_data['puzzle']['key'], level_data['puzzle']['letter_mappings'])
  32. self.screen = 'puzzle'
  33. else:
  34. self.on_story_puzzle_completion()
  35. def open_main_menu(self):
  36. self.mode = None
  37. self.menu = new_main_menu(self)
  38. self.screen = 'menu'
  39. def open_pause_menu(self):
  40. self.menu = new_pause_menu(self)
  41. self.screen = 'menu'
  42. def open_how_to_play_dialog(self):
  43. self.dialog_list.append(new_how_to_play_dialog(self))
  44. def open_about_dialog(self):
  45. self.dialog_list.append(new_about_dialog(self))
  46. def open_game_over_dialog(self):
  47. self.dialog_list.append(new_game_over_dialog(self))
  48. if self.mode == 'story':
  49. self.on_story_puzzle_completion()
  50. else:
  51. self.start_new_puzzle()
  52. def on_story_puzzle_completion(self):
  53. if self.level['id'] not in self.level_history['completed_levels']:
  54. self.level_history['completed_levels'].append(self.level['id'])
  55. if self.level['post_narrative'] is not None:
  56. self.dialog_list.append(Dialog(self.level['post_narrative'], self))
  57. if self.level['ending'] is not None:
  58. if self.level['ending'] == 'win':
  59. self.dialog_list.append(Dialog(['You win!'], self))
  60. elif self.level['ending'] == 'lose':
  61. self.dialog_list.append(Dialog(['You lose!'], self))
  62. else:
  63. self.dialog_list.append(Dialog(['Stalemate!'], self))
  64. self.mode = None
  65. self.story = None
  66. self.level = None
  67. self.open_main_menu()
  68. elif self.level['next_level'] is not None:
  69. self.load_story_level(self.story, self.level['next_level'], False)
  70. elif self.level['choices'] is not None:
  71. self.screen = 'menu'
  72. self.menu = new_story_branch_menu(self, self.level['choices'])
  73. else:
  74. print("This should not be possible.")
  75. def show_hint(self):
  76. self.puzzle.show_hint()
  77. if self.puzzle.is_puzzle_solved():
  78. self.open_game_over_dialog()
  79. def start_over(self):
  80. if self.mode == 'story':
  81. self.puzzle.start_over(self.level['puzzle']['letter_mappings'])
  82. else:
  83. self.puzzle.start_over()
  84. def load_puzzle(self, puzzle):
  85. self.puzzle = puzzle
  86. def start_new_puzzle(self):
  87. self.puzzle = new_puzzle(self.data_manager, self.user_preferences.quote_db)
  88. def page_puzzle(self, direction):
  89. pages_data = self.get_puzzle_output_pages()
  90. current_page_index = pages_data['cursor_page']
  91. pages = pages_data['pages']
  92. max_page = len(pages)
  93. if len(pages) <= 1:
  94. return
  95. if direction == -1:
  96. if current_page_index == 0:
  97. next_page_index = max_page - 1
  98. else:
  99. next_page_index = current_page_index - 1
  100. else:
  101. if current_page_index + 1 == max_page:
  102. next_page_index = 0
  103. else:
  104. next_page_index = current_page_index + 1
  105. p = pages[next_page_index]
  106. original_indices = [i['original_index'] for i in p if 'original_index' in i and i['guess_letter'] != ' ']
  107. if len(original_indices) == 0:
  108. # In this case, there is no valid cursor index on the next page. The logic to resolve this is complex, so just give up.
  109. return
  110. else:
  111. new_index = min(original_indices)
  112. self.puzzle.jump_cursor(new_index)
  113. def get_puzzle_output_pages(self):
  114. max_width = self.puzzle_dimensions['puzzle_wrap_width']
  115. max_height = self.puzzle_dimensions['wrap_height']
  116. guess_text = self.puzzle.apply_guess()
  117. puzzle_text = self.puzzle.encrypted_text
  118. red_marks = self.puzzle.get_red_marks()
  119. x_offset_count = 0
  120. y_offset_count = 0
  121. page_offset_count = 0
  122. pages = []
  123. current_page = []
  124. cursor_page = -1
  125. current_page_index = 0
  126. for line in wrap_puzzle_text(puzzle_text, max_width):
  127. if y_offset_count >= max_height:
  128. y_offset_count = 0
  129. current_page_index += 1
  130. pages.append(current_page)
  131. current_page = []
  132. for x_offset_count, puzzle_letter_wrapped in enumerate(line):
  133. puzzle_letter = puzzle_letter_wrapped['char']
  134. if ('original_index' in puzzle_letter_wrapped):
  135. i = puzzle_letter_wrapped['original_index']
  136. guess_letter = guess_text[i]
  137. is_red_mark = red_marks[i] == 'r'
  138. is_cursor = self.puzzle.cursor_position == i
  139. else:
  140. guess_letter = ' '
  141. is_red_mark = False
  142. is_cursor = False
  143. if is_cursor:
  144. cursor_page = current_page_index
  145. puzzle_element = {
  146. 'x_offset': x_offset_count,
  147. 'y_offset': y_offset_count,
  148. 'is_cursor': is_cursor,
  149. 'is_red_mark': is_red_mark,
  150. 'puzzle_letter': puzzle_letter,
  151. 'guess_letter': guess_letter
  152. }
  153. if ('original_index' in puzzle_letter_wrapped):
  154. puzzle_element['original_index'] = puzzle_letter_wrapped['original_index']
  155. current_page.append(puzzle_element)
  156. y_offset_count += 1
  157. if len(current_page) > 0:
  158. pages.append(current_page)
  159. return { 'cursor_page': cursor_page, 'pages': pages }
  160. def update_state(self, button):
  161. """Updates game state based on button inputs."""
  162. if self.screen == 'exit_to_os':
  163. return
  164. elif len(self.dialog_list) > 0:
  165. if button == 'a':
  166. self.dialog_list[0].select()
  167. elif button == 'b':
  168. self.dialog_list[0].cancel()
  169. return
  170. elif self.screen == 'menu':
  171. # Menu navigation logic
  172. if button == 'up':
  173. self.menu.navigate(-1)
  174. elif button == 'down':
  175. self.menu.navigate(1)
  176. elif button == 'a':
  177. self.menu.select()
  178. elif button == 'b':
  179. self.menu.cancel()
  180. return
  181. # Game state logic (only applies when no menu is open)
  182. guess_changed = False
  183. if button == 'left':
  184. self.puzzle.shift_cursor(direction=-1)
  185. elif button == 'right':
  186. self.puzzle.shift_cursor(direction=1)
  187. elif button == 'up':
  188. self.puzzle.shift_guess(direction=1)
  189. guess_changed = True
  190. elif button == 'down':
  191. self.puzzle.shift_guess(direction=-1)
  192. guess_changed = True
  193. elif button == 'a':
  194. self.page_puzzle(direction=1)
  195. elif button == 'b':
  196. self.page_puzzle(direction=-1)
  197. elif button == 'm':
  198. self.open_pause_menu()
  199. if guess_changed and self.puzzle.is_puzzle_solved():
  200. self.open_game_over_dialog()