A generator for *Star Wars* interstellar shipping and passenger jobs, based loosely on the missions you can take on in *Escape Velocity* and *Endless Space*.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

swjg.py 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import argparse
  2. import re
  3. from enum import Enum
  4. from string import Template
  5. from typing import NamedTuple
  6. from jinja2 import Environment, PackageLoader, select_autoescape
  7. import items
  8. from destinations import DESTINATIONS, SYSTEMS, Destination, DestinationType
  9. # global variables
  10. env = Environment(loader=PackageLoader("swjg"), autoescape=select_autoescape)
  11. parser = argparse.ArgumentParser()
  12. # helper definitions
  13. def get_identifiers_from_template(template):
  14. # Template.get_identifiers() doesn't appear until Python 3.11
  15. # Don't substitute anything; get the raw string
  16. template_str = Template.safe_substitute()
  17. matches = re.findall("\$[a-z0-9_\.]+", template_str)
  18. matches = [x[1:] for x in matches]
  19. return matches
  20. def get_destination_type_from_enum(destination):
  21. dtype = destination.type
  22. dtype_name = dtype._name_
  23. dtype_name_lower = dtype_name.lower()
  24. dtype_name_nou = dtype_name_lower.replace("_", " ")
  25. if dtype_name_nou[0] in ["a", "e", "i", "o", "u"]:
  26. # it's only "asteroid" and "ocean" now but it might expand
  27. dtype_name_final = "an " + dtype_name_nou
  28. else:
  29. dtype_name_final = "a " + dtype_name_nou
  30. return dtype_name_final
  31. # Class definitions
  32. class Mission:
  33. output_destination_template = Template(
  34. "to $world $destination_type in the $system system"
  35. )
  36. output_sector_template = Template(", part of the $sector,")
  37. value_template = Template("for $value credits")
  38. error_template = Template(
  39. "You haven't given this mission some of the values it needs. Please check: $potential_values"
  40. )
  41. def __init__(self, destination: Destination, value: int, *args, **kwargs):
  42. # destination: Destination (see above)
  43. self.world = destination.name if destination.name is not None else None
  44. self.system = (
  45. destination.system if destination.system is not None else destination.name
  46. )
  47. self.sector = destination.sector if destination.sector is not None else None
  48. self.destination_type = get_destination_type_from_enum(destination)
  49. self.value = value
  50. def assemble_templates(self) -> list:
  51. # Override this in children
  52. template_list = []
  53. if self.world != None:
  54. template_list.append(self.output_destination_template)
  55. if self.sector != None:
  56. template_list.append(self.output_sector_template)
  57. template_list.append(self.value_template)
  58. return template_list
  59. def get_current_values(self) -> dict:
  60. return {
  61. "world": self.world + "," if self.world != "" else "",
  62. "system": self.system,
  63. "sector": self.sector,
  64. "destination_type": self.destination_type,
  65. "value": self.value,
  66. }
  67. def missing_values(self) -> list:
  68. object_vars = vars(self)
  69. missing = []
  70. for key, val in object_vars.items():
  71. if val is None and key != "time": # time is optional
  72. missing.append(key)
  73. return missing
  74. def assemble_text(self) -> str:
  75. missing_vals = self.missing_values()
  76. templates = self.assemble_templates()
  77. if (
  78. len(templates) == 0 or len(missing_vals) != 0
  79. ): # either both of these should be true or neither should
  80. raise ValueError(
  81. self.error_template.substitute(potential_values=missing_vals)
  82. )
  83. current_values = self.get_current_values()
  84. output = []
  85. for template in templates:
  86. output.append(template.substitute(**current_values))
  87. out_text = " ".join(output)
  88. out_text = out_text.replace(" ,", ",")
  89. out_text = out_text.replace(" ", " ") # template issue
  90. out_text += "."
  91. return out_text
  92. class PassengerMission(Mission):
  93. output_initial_template = Template("Bring $number passenger$plural")
  94. def __init__(self, number: int, *args, **kwargs):
  95. super(PassengerMission, self).__init__(*args, **kwargs)
  96. self.number = number
  97. def get_current_values(self) -> dict:
  98. base_dict = super(PassengerMission, self).get_current_values()
  99. new_dict = {"number": self.number, "plural": "" if self.number == 1 else "s"}
  100. base_dict.update(new_dict)
  101. return base_dict
  102. def assemble_templates(self):
  103. template_list = [self.output_initial_template]
  104. template_list.extend(super(PassengerMission, self).assemble_templates())
  105. return template_list
  106. def assemble_text(self) -> str:
  107. return super(PassengerMission, self).assemble_text()
  108. class CargoMission(Mission):
  109. output_initial_template = Template("Deliver $tons ton$plural of $item")
  110. output_time_timeplate = Template("in the next $time days")
  111. def __init__(self, tons: int, item: str, time: int | None, *args, **kwargs):
  112. # tons: integer
  113. # item: string (this will not be pluralized in the text)
  114. # time: integer (number of days the crew have to complete delivery)
  115. # value: integer (value in credits)
  116. super(CargoMission, self).__init__(*args, **kwargs)
  117. self.tons = tons
  118. self.item = item
  119. self.time = time
  120. if self.tons == 1:
  121. self.plural = ""
  122. else:
  123. self.plural = "s"
  124. def get_current_values(self) -> dict:
  125. base_dict = super(CargoMission, self).get_current_values()
  126. new_dict = {
  127. "tons": self.tons,
  128. "item": self.item,
  129. "time": self.time,
  130. "plural": "" if self.tons == 1 else "s",
  131. }
  132. base_dict.update(new_dict)
  133. return base_dict
  134. def assemble_templates(self):
  135. template_list = [self.output_initial_template]
  136. if self.time is not None:
  137. template_list.append(self.output_time_timeplate)
  138. template_list.extend(super(CargoMission, self).assemble_templates())
  139. return template_list
  140. def assemble_text(self) -> str:
  141. return super(CargoMission, self).assemble_text()
  142. class GroupMission(Mission):
  143. output_initial_template = Template("Bring $number member$plural of a $group")
  144. def __init__(self, number: int, group: str, *args, **kwargs):
  145. super(GroupMission, self).__init__(*args, **kwargs)
  146. self.number = number
  147. self.group = group
  148. def get_current_values(self) -> dict:
  149. base_dict = super(GroupMission, self).get_current_values()
  150. new_dict = {
  151. "number": self.number,
  152. "group": self.group,
  153. "plural": "" if self.number == 1 else "s",
  154. }
  155. base_dict.update(new_dict)
  156. return base_dict
  157. def assemble_templates(self):
  158. template_list = [self.output_initial_template]
  159. template_list.extend(super(GroupMission, self).assemble_templates())
  160. return template_list
  161. def assemble_text(self) -> str:
  162. return super(GroupMission, self).assemble_text()
  163. # the function that does the thing
  164. def main():
  165. import random
  166. mission_types = [PassengerMission, CargoMission, CargoMission, GroupMission]
  167. missions = []
  168. for _ in range(10):
  169. current_mission = random.choice(mission_types)
  170. dest_type = random.randint(1, 5)
  171. if dest_type < 4: # world, 80% chance
  172. destination = random.choice(DESTINATIONS)
  173. else:
  174. system = random.choice(SYSTEMS)
  175. destination = Destination(
  176. name="",
  177. system=system[0],
  178. sector=system[1],
  179. type=(
  180. DestinationType.STATION
  181. if random.randint(0, 1) == 0
  182. else DestinationType.ASTEROID_BASE
  183. ),
  184. )
  185. item, time, tons, number, value, group = [None] * 6
  186. if current_mission == CargoMission:
  187. current_item = random.choice(items.ITEMS)
  188. tons = random.randint(
  189. current_item.potential_tons[0], current_item.potential_tons[1]
  190. )
  191. value = random.randint(*current_item.potential_values) * 1000
  192. item = current_item.name
  193. if current_item.potential_times is not None:
  194. if current_item.chance_for_time is not None:
  195. is_there_a_time = random.random() * 100
  196. # print(current_item.chance_for_time, is_there_a_time, is_there_a_time < current_item.chance_for_time)
  197. if is_there_a_time < current_item.chance_for_time:
  198. time = random.randint(
  199. current_item.potential_times[0],
  200. current_item.potential_times[1],
  201. )
  202. else:
  203. number = random.randint(1, 8)
  204. time = random.randint(7, 31)
  205. value = random.randint(20, 120) * 1000
  206. if current_mission == GroupMission:
  207. group = random.choice(["family", "performing troupe", "acrobatic troupe"])
  208. missions.append(
  209. current_mission(
  210. destination=destination,
  211. number=number,
  212. tons=tons,
  213. item=item,
  214. time=time,
  215. value=value,
  216. group=group,
  217. )
  218. )
  219. for mission in missions:
  220. # print(mission, mission.__dict__)
  221. print(mission.assemble_text())
  222. # Don't do anything if the module is loaded wholesale into something else
  223. if __name__ == "__main__":
  224. main()