landing.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. '''
  2. Created on 2017-02-01
  3. @author: olivier.massot
  4. '''
  5. import math
  6. import sys
  7. G = -3.711
  8. # surface_n = int(input()) # the number of points used to draw the surface of Mars.
  9. # land = [ int(input().split()[1]) for _ in range(surface_n)]
  10. class V(tuple):
  11. """ 2D vector (dx, dy)
  12. where dx and dy are float """
  13. def __new__(cls, dx, dy):
  14. return tuple.__new__(cls, tuple([float(dx), float(dy)]))
  15. @property
  16. def dx(self):
  17. return self.__getitem__(0)
  18. @property
  19. def dy(self):
  20. return self.__getitem__(1)
  21. @property
  22. def angle(self):
  23. return math.atan2(self.dy, self.dx)
  24. @property
  25. def amplitude(self):
  26. return math.hypot(self.dx, self.dy)
  27. def unit_vector(self):
  28. return self / self.amplitude
  29. def __repr__(self):
  30. return "<Vector ({}, {})>".format(self.dx, self.dy)
  31. def __getitem__(self, key):
  32. return super(V, self).__getitem__(key)
  33. def __add__(self, value):
  34. if isinstance(value, self.__class__):
  35. return V(self.dx + value.dx, self.dy + value.dy)
  36. elif isinstance(value, int) or isinstance(value, float):
  37. return V(self.dx + value, self.dy + value)
  38. else:
  39. raise TypeError("value has to be a V instance, or a numeric (given: {})".format(value))
  40. def __sub__(self, value):
  41. if isinstance(value, self.__class__):
  42. return V(self.dx - value.dx, self.dy - value.dy)
  43. elif isinstance(value, int) or isinstance(value, float):
  44. return V(self.dx - value, self.dy - value)
  45. else:
  46. raise TypeError("value has to be a V instance, or a numeric (given: {})".format(value))
  47. def __mul__(self, value) :
  48. return V(self.dx * value, self.dy * value)
  49. def __truediv__(self, value) :
  50. return V(self.dx / value, self.dy / value)
  51. @classmethod
  52. def from_to(cls, point1, point2):
  53. return cls(*point2) - cls(*point1)
  54. class Point(tuple):
  55. """ 2D point """
  56. def __new__(cls, x, y):
  57. return tuple.__new__(cls, tuple([int(x), int(y)]))
  58. def __repr__(self):
  59. return "<Point ({}, {})>".format(self.x, self.y)
  60. @property
  61. def x(self):
  62. return self.__getitem__(0)
  63. @x.setter
  64. def x(self, x):
  65. return self.__setitem__(0, x)
  66. @property
  67. def y(self):
  68. return self.__getitem__(1)
  69. @y.setter
  70. def y(self, y):
  71. return self.__setitem__(1, y)
  72. def distance_to(self, other_point):
  73. return V(other_point.x - self.x, other_point.y - self.y).amplitude
  74. class Landscape(list):
  75. def __init__(self, *args):
  76. super(Landscape, self).__init__(*args)
  77. @classmethod
  78. def x_index(cls, x):
  79. return int(x / 1000)
  80. def y(self, x):
  81. return self.__getitem__(self.x_index(x))
  82. @property
  83. def coordinates(self):
  84. return [Point(index * 1000, y) for index, y in enumerate(self)]
  85. def landing_zones(self):
  86. """ "find the index of flat zones where rover can land
  87. (index is the position of the left point of the zone in the 'land' list) """
  88. return [ index for index in range(len(land) - 1) if land[index] == land[index + 1] ]
  89. def nearest_landing_point(self, from_x):
  90. """return the coordinates of the middle of the
  91. nearest landing zone as a tuple (x, y)
  92. for zone going from land[index] to land[index + 1] """
  93. index = sorted(self.landing_zones(), key=lambda x: abs(1000 * x - from_x))[0]
  94. return Point((1000 * index + 500), self.__getitem__(index))
  95. def next_top(self, from_x, to_x):
  96. """return the next peak as a tuple (x, y) """
  97. if from_x > to_x:
  98. from_x, to_x = to_x, from_x
  99. try:
  100. return Point(max([(1000 * index, y) for index, y in enumerate(self) \
  101. if to_x > (1000 * index) > from_x], key=lambda x: x[1]))
  102. except ValueError:
  103. return None
  104. class MarsLander(object):
  105. max_h_landing_speed = 20.0
  106. max_v_landing_speed = 40.0
  107. minimal_flight_altitude = 60
  108. def __init__(self):
  109. self._x = 0
  110. self._y = 0
  111. self._h_speed = 0.0
  112. self._v_speed = 0.0
  113. self._power = 0
  114. self._rotation = 0
  115. self._fuel = 0
  116. def __setattr__(self, attr, val):
  117. """ override object __setattr__ method """
  118. # check that the new value has the same type as the old one
  119. try:
  120. prev = type(self.__getattribute__(attr))
  121. if not isinstance(val, prev):
  122. raise TypeError("{attribute} has to be an {previous_type} (given: {value})\
  123. ".format(attribute=attr, value=val, previous_type=prev))
  124. except AttributeError:
  125. # newly created attribute
  126. pass
  127. object.__setattr__(self, attr, val)
  128. def __repr__(self):
  129. return "<MarsLander: {}>".format("; ".join(sorted(["{} = {}".format(key, val) for key, val in self.__dict__.items()])))
  130. @classmethod
  131. def from_input(cls, instr):
  132. """ load the parameters from the given input """
  133. lander = cls()
  134. lander.x, lander.y, h_speed, v_speed, lander.fuel, lander.rotation, lander.power = [int(i) for i in instr.split()]
  135. lander.h_speed, lander.v_speed = float(h_speed), float(v_speed)
  136. return lander
  137. @property
  138. def x(self):
  139. return self._x
  140. @x.setter
  141. def x(self, val):
  142. if not val in range(7000):
  143. raise ValueError("x has to be an integer in range 0 to 6999 (given: {value})".format(value=val))
  144. self._x = val
  145. @property
  146. def y(self):
  147. return self._y
  148. @y.setter
  149. def y(self, val):
  150. if not val in range(3000):
  151. raise ValueError("y has to be an integer in range 0 to 2999 (given: {value})".format(value=val))
  152. self._y = val
  153. @property
  154. def position(self):
  155. return Point(self.x, self.y)
  156. @property
  157. def h_speed(self):
  158. return self._h_speed
  159. @h_speed.setter
  160. def h_speed(self, val):
  161. # if not val in range(-500, 500):
  162. # raise ValueError("h_speed has to be an integer in range -499 to 499 (given: {value})".format(value=val))
  163. self._h_speed = val
  164. @property
  165. def v_speed(self):
  166. return self._v_speed
  167. @v_speed.setter
  168. def v_speed(self, val):
  169. # if not val in range(-500, 500):
  170. # raise ValueError("v_speed has to be an integer in range -499 to 499 (given: {value})".format(value=val))
  171. self._v_speed = val
  172. @property
  173. def speed_vector(self):
  174. return V(self.h_speed, self.v_speed)
  175. @property
  176. def power(self):
  177. return self._power
  178. @power.setter
  179. def power(self, val):
  180. if not val in range(5):
  181. raise ValueError("power has to be an integer in range 0 to 4 (given: {value})".format(value=val))
  182. self._power = val
  183. @property
  184. def rotation(self):
  185. return self._rotation
  186. @rotation.setter
  187. def rotation(self, val):
  188. if not val in range(-91, 91):
  189. raise ValueError("rotation has to be an integer in range -90 to 90 (given: {value})".format(value=val))
  190. self._rotation = val
  191. @property
  192. def fuel(self):
  193. return self._fuel
  194. @fuel.setter
  195. def fuel(self, val):
  196. if not val in range(2001):
  197. raise ValueError("fuel has to be an integer in range 0 to 2000 (given: {value})".format(value=val))
  198. self._fuel = val
  199. @classmethod
  200. def compute_acceleration(cls, power, rotation):
  201. """ return the resulting acceleration as a 2d vector (ax, ay)
  202. with that power / rotation configuration"""
  203. return V(-1 * math.sin(math.radians(rotation)) * power, math.cos(math.radians(rotation)) * power + G)
  204. @property
  205. def acceleration(self):
  206. """ return the current acceleration as a 2d vector (ax, ay) """
  207. return self.compute_acceleration(self.power, self.rotation)
  208. def speed_in(self, t):
  209. """ compute the speed in 't' seconds with the same acceleration,
  210. returns a 2d vector (vx, vy) """
  211. if not isinstance(t, int):
  212. raise TypeError("'t' has to be an integer (given:{})".format(t))
  213. if not t >= 0:
  214. raise ValueError("'t' has to be positive (given:{})".format(t))
  215. ax, ay = self.acceleration
  216. return V(self.h_speed + ax * t, self.v_speed + ay * t)
  217. def update_speed(self, dt):
  218. """ update the current speed, after 't' sec. with current acceleration """
  219. self.h_speed, self.v_speed = self.speed_in(dt)
  220. def configurations(self):
  221. """return the available (power, rotation) depending on the current configuration"""
  222. return [(p, 15 * r) for p in range(5) for r in range(-6, 7)]
  223. # if abs(p - self.power) <= 1 and abs(15 * r - self.rotation) <= 15]
  224. def compute_trajectory(self, landscape):
  225. """ return the route to follow to each the landing point
  226. as a list of Points
  227. The last point will be above the landing point of 'minimal_flight_altitude'
  228. If that point has been reached, return an empty list"""
  229. landing_vector = V.from_to(self.position, landscape.nearest_landing_point(self.x))
  230. steps = []
  231. if abs(landing_vector.dx) < 100 and abs(landing_vector.dy) <= self.minimal_flight_altitude:
  232. # landing point has been reached
  233. return []
  234. # could be shorter, could be quicker, but robustness and clarity are needed
  235. for point in landscape.coordinates:
  236. top_vector = V.from_to(self.position, point) + V(0, self.minimal_flight_altitude)
  237. if top_vector.dx * landing_vector.dx < 0:
  238. # top is not in the landing point direction
  239. continue
  240. if abs(top_vector.dx) > abs(landing_vector.dx):
  241. # too far
  242. continue
  243. if (landing_vector * top_vector.dx / abs(landing_vector.dx)).dy > top_vector.dy:
  244. # top is under the trajectory
  245. continue
  246. steps.append(Point(*(V(*self.position) + top_vector)))
  247. steps.sort(key=lambda p: p.x, reverse=(landing_vector.dx < 0))
  248. last_step_vector = (V(*self.position) + landing_vector + V(0, self.minimal_flight_altitude))
  249. steps.append(Point(*last_step_vector))
  250. return steps
  251. def land(self, landscape, callback=None, output=None):
  252. """ (generator)
  253. Mars lander will land on the given landscape
  254. """
  255. # callback function is called at each iteration
  256. if not callback:
  257. callback = (lambda x: x)
  258. # output function is called to print lander's thoughts
  259. if not output:
  260. output = (lambda x: print(x))
  261. t = 0
  262. while 1:
  263. t += 1
  264. output("Hi, this is Mars Lander, and it is {} s.".format(t))
  265. output("I am currently at ({} , {})".format(self.x, self.y))
  266. trajectory = self.compute_trajectory(landscape)
  267. output("My trajectory is {}".format(trajectory))
  268. try:
  269. target = trajectory[0]
  270. output("For now, I try to reach {}".format(target))
  271. except IndexError:
  272. output("Right: I'm landing right now")
  273. self.power = 4 if self.v_speed > 1 else 3
  274. self.rotation = 0
  275. callback(self)
  276. yield t
  277. continue
  278. target_direction = V.from_to(self.position, target).unit_vector()
  279. output("Direction unit vector is {}".format(target_direction))
  280. # current speed vector
  281. output("My current speed vector is {}".format(self.speed_vector))
  282. # optimal speed vector
  283. if abs(target_direction.dx * self.max_h_landing_speed) >= abs(target_direction.dy * self.max_v_landing_speed):
  284. needed_speed_vector = target_direction * self.max_h_landing_speed
  285. else:
  286. needed_speed_vector = target_direction * self.max_v_landing_speed
  287. output("I need a speed like: {}".format(needed_speed_vector))
  288. # compute the new acceleration vector
  289. new_acc_vector = needed_speed_vector - self.speed_vector
  290. output("I can reach that with that acceleration: {}".format(new_acc_vector))
  291. # look for the closest possible acceleration (p is set to 0.1 instead of 0 in order to give sense to vectors comparison)
  292. configs = [(p, r) for p in (0.1, 1, 2, 3, 4) for r in range(-90, 91, 15)]
  293. closest_config = min([(p, r, self.compute_acceleration(p, r)) for p, r in configs], \
  294. key=lambda c: Point(*new_acc_vector).distance_to(Point(*c[2])))
  295. power, rotation, new_acc_vector = closest_config
  296. if power < 1:
  297. power = 0
  298. output("The closest acceleration I could reach is {} ms-2, with power {} and rotation {}".format(new_acc_vector, power, rotation))
  299. # limits the changes if needed
  300. if abs(power - self.power) > 1 or abs(rotation - self.rotation) > 15:
  301. if power != self.power:
  302. power = self.power + int((power - self.power) / abs(power - self.power))
  303. if rotation != self.rotation:
  304. rotation = self.rotation + 15 * int((rotation - self.rotation) / abs(rotation - self.rotation))
  305. new_acc_vector = self.compute_acceleration(power, rotation)
  306. output("Hmmph, the best I can reach is power {}, rotation {}, for an acceleration of {} ms-2".format(power, rotation, new_acc_vector))
  307. # update the lander attributes
  308. self.power = power
  309. self.rotation = rotation
  310. self.fuel -= power
  311. # new position
  312. new_x, new_y = V(*self.position) + self.speed_vector
  313. self.x, self.y = int(new_x), int(new_y)
  314. # new speed
  315. self.h_speed, self.v_speed = self.speed_vector + new_acc_vector
  316. callback(self)
  317. yield t
  318. def hyperspace(self):
  319. raise NotImplementedError()
  320. def distance_to(self, x, y):
  321. return math.hypot((x - self.x), (y - self.y))
  322. def angle_to(self, x, y):
  323. return math.atan2((y - self.y), (x - self.x))
  324. def send_data(lander):
  325. print("{rotation} {power}".format(rotation=lander.rotation, power=lander.power))
  326. # input()
  327. def output(msg):
  328. print(msg, file=sys.stderr)
  329. surface_n = 7
  330. land = [150, 500, 2000, 150, 150, 500, 2000]
  331. # land = [3000, 150, 150, 500, 3000, 600, 1000]
  332. landscape = Landscape(land)
  333. lander = MarsLander()
  334. lander.x = 3000
  335. lander.y = 2000
  336. lander.fuel = 1000
  337. gen = lander.land(landscape)
  338. for i in range(5):
  339. t = next(gen)
  340. print(t, lander)
  341. input()
  342. # lander = MarsLander.from_input( input() )
  343. # landing_point_x, landing_point_y = nearest_landing_zone(lander.x)
  344. # target_x, target_y = landing_point_x, landing_point_y
  345. # #move_generator = lander.move_to(target_x, target_y, callback=send_data, landing=True, output=output, land_max_elevation=max(land))
  346. #
  347. # while True:
  348. # target = next_peak(lander.x, landing_point_x)
  349. # if target:
  350. # if target != (target_x, target_y):
  351. # target_x, target_y = target
  352. # move_generator = lander.move_to(target_x, target_y + 100, callback=send_data, landing=False)
  353. # else:
  354. # move_generator = lander.move_to(landing_point_x, landing_point_y, callback=send_data, landing=True)
  355. #
  356. # t = next(move_generator)
  357. # print(t, lander, file=sys.stderr)
  358. #
  359. # x, y, h_speed, v_speed, fuel, rotate, power = [int(i) for i in input().split()]
  360. # #print(x, y, h_speed, v_speed, fuel, rotate, power, file=sys.stderr)
  361. #
  362. #
  363. # # rotate power. rotate is the desired rotation angle. power is the desired thrust power.
  364. # # print("{rotation} {power}".format(rotation=lander.rotation, power=lander.power))
  365. #
  366. # #input() # get the unused input
  367. # #t = next(move)