''' Created on 2017-02-01 @author: olivier.massot ''' import math import sys G = -3.711 # surface_n = int(input()) # the number of points used to draw the surface of Mars. # land = [ int(input().split()[1]) for _ in range(surface_n)] class V(tuple): """ 2D vector (dx, dy) where dx and dy are float """ def __new__(cls, dx, dy): return tuple.__new__(cls, tuple([float(dx), float(dy)])) @property def dx(self): return self.__getitem__(0) @property def dy(self): return self.__getitem__(1) @property def angle(self): return math.atan2(self.dy, self.dx) @property def amplitude(self): return math.hypot(self.dx, self.dy) def unit_vector(self): return self / self.amplitude def __repr__(self): return "".format(self.dx, self.dy) def __getitem__(self, key): return super(V, self).__getitem__(key) def __add__(self, value): if isinstance(value, self.__class__): return V(self.dx + value.dx, self.dy + value.dy) elif isinstance(value, int) or isinstance(value, float): return V(self.dx + value, self.dy + value) else: raise TypeError("value has to be a V instance, or a numeric (given: {})".format(value)) def __sub__(self, value): if isinstance(value, self.__class__): return V(self.dx - value.dx, self.dy - value.dy) elif isinstance(value, int) or isinstance(value, float): return V(self.dx - value, self.dy - value) else: raise TypeError("value has to be a V instance, or a numeric (given: {})".format(value)) def __mul__(self, value) : return V(self.dx * value, self.dy * value) def __truediv__(self, value) : return V(self.dx / value, self.dy / value) @classmethod def from_to(cls, point1, point2): return cls(*point2) - cls(*point1) class Point(tuple): """ 2D point """ def __new__(cls, x, y): return tuple.__new__(cls, tuple([int(x), int(y)])) def __repr__(self): return "".format(self.x, self.y) @property def x(self): return self.__getitem__(0) @x.setter def x(self, x): return self.__setitem__(0, x) @property def y(self): return self.__getitem__(1) @y.setter def y(self, y): return self.__setitem__(1, y) def distance_to(self, other_point): return V(other_point.x - self.x, other_point.y - self.y).amplitude class Landscape(list): def __init__(self, *args): super(Landscape, self).__init__(*args) @classmethod def x_index(cls, x): return int(x / 1000) def y(self, x): return self.__getitem__(self.x_index(x)) @property def coordinates(self): return [Point(index * 1000, y) for index, y in enumerate(self)] def landing_zones(self): """ "find the index of flat zones where rover can land (index is the position of the left point of the zone in the 'land' list) """ return [ index for index in range(len(land) - 1) if land[index] == land[index + 1] ] def nearest_landing_point(self, from_x): """return the coordinates of the middle of the nearest landing zone as a tuple (x, y) for zone going from land[index] to land[index + 1] """ index = sorted(self.landing_zones(), key=lambda x: abs(1000 * x - from_x))[0] return Point((1000 * index + 500), self.__getitem__(index)) def next_top(self, from_x, to_x): """return the next peak as a tuple (x, y) """ if from_x > to_x: from_x, to_x = to_x, from_x try: return Point(max([(1000 * index, y) for index, y in enumerate(self) \ if to_x > (1000 * index) > from_x], key=lambda x: x[1])) except ValueError: return None class MarsLander(object): max_h_landing_speed = 20.0 max_v_landing_speed = 40.0 minimal_flight_altitude = 60 def __init__(self): self._x = 0 self._y = 0 self._h_speed = 0.0 self._v_speed = 0.0 self._power = 0 self._rotation = 0 self._fuel = 0 def __setattr__(self, attr, val): """ override object __setattr__ method """ # check that the new value has the same type as the old one try: prev = type(self.__getattribute__(attr)) if not isinstance(val, prev): raise TypeError("{attribute} has to be an {previous_type} (given: {value})\ ".format(attribute=attr, value=val, previous_type=prev)) except AttributeError: # newly created attribute pass object.__setattr__(self, attr, val) def __repr__(self): return "".format("; ".join(sorted(["{} = {}".format(key, val) for key, val in self.__dict__.items()]))) @classmethod def from_input(cls, instr): """ load the parameters from the given input """ lander = cls() lander.x, lander.y, h_speed, v_speed, lander.fuel, lander.rotation, lander.power = [int(i) for i in instr.split()] lander.h_speed, lander.v_speed = float(h_speed), float(v_speed) return lander @property def x(self): return self._x @x.setter def x(self, val): if not val in range(7000): raise ValueError("x has to be an integer in range 0 to 6999 (given: {value})".format(value=val)) self._x = val @property def y(self): return self._y @y.setter def y(self, val): if not val in range(3000): raise ValueError("y has to be an integer in range 0 to 2999 (given: {value})".format(value=val)) self._y = val @property def position(self): return Point(self.x, self.y) @property def h_speed(self): return self._h_speed @h_speed.setter def h_speed(self, val): # if not val in range(-500, 500): # raise ValueError("h_speed has to be an integer in range -499 to 499 (given: {value})".format(value=val)) self._h_speed = val @property def v_speed(self): return self._v_speed @v_speed.setter def v_speed(self, val): # if not val in range(-500, 500): # raise ValueError("v_speed has to be an integer in range -499 to 499 (given: {value})".format(value=val)) self._v_speed = val @property def speed_vector(self): return V(self.h_speed, self.v_speed) @property def power(self): return self._power @power.setter def power(self, val): if not val in range(5): raise ValueError("power has to be an integer in range 0 to 4 (given: {value})".format(value=val)) self._power = val @property def rotation(self): return self._rotation @rotation.setter def rotation(self, val): if not val in range(-91, 91): raise ValueError("rotation has to be an integer in range -90 to 90 (given: {value})".format(value=val)) self._rotation = val @property def fuel(self): return self._fuel @fuel.setter def fuel(self, val): if not val in range(2001): raise ValueError("fuel has to be an integer in range 0 to 2000 (given: {value})".format(value=val)) self._fuel = val @classmethod def compute_acceleration(cls, power, rotation): """ return the resulting acceleration as a 2d vector (ax, ay) with that power / rotation configuration""" return V(-1 * math.sin(math.radians(rotation)) * power, math.cos(math.radians(rotation)) * power + G) @property def acceleration(self): """ return the current acceleration as a 2d vector (ax, ay) """ return self.compute_acceleration(self.power, self.rotation) def speed_in(self, t): """ compute the speed in 't' seconds with the same acceleration, returns a 2d vector (vx, vy) """ if not isinstance(t, int): raise TypeError("'t' has to be an integer (given:{})".format(t)) if not t >= 0: raise ValueError("'t' has to be positive (given:{})".format(t)) ax, ay = self.acceleration return V(self.h_speed + ax * t, self.v_speed + ay * t) def update_speed(self, dt): """ update the current speed, after 't' sec. with current acceleration """ self.h_speed, self.v_speed = self.speed_in(dt) def configurations(self): """return the available (power, rotation) depending on the current configuration""" return [(p, 15 * r) for p in range(5) for r in range(-6, 7)] # if abs(p - self.power) <= 1 and abs(15 * r - self.rotation) <= 15] def compute_trajectory(self, landscape): """ return the route to follow to each the landing point as a list of Points The last point will be above the landing point of 'minimal_flight_altitude' If that point has been reached, return an empty list""" landing_vector = V.from_to(self.position, landscape.nearest_landing_point(self.x)) steps = [] if abs(landing_vector.dx) < 100 and abs(landing_vector.dy) <= self.minimal_flight_altitude: # landing point has been reached return [] # could be shorter, could be quicker, but robustness and clarity are needed for point in landscape.coordinates: top_vector = V.from_to(self.position, point) + V(0, self.minimal_flight_altitude) if top_vector.dx * landing_vector.dx < 0: # top is not in the landing point direction continue if abs(top_vector.dx) > abs(landing_vector.dx): # too far continue if (landing_vector * top_vector.dx / abs(landing_vector.dx)).dy > top_vector.dy: # top is under the trajectory continue steps.append(Point(*(V(*self.position) + top_vector))) steps.sort(key=lambda p: p.x, reverse=(landing_vector.dx < 0)) last_step_vector = (V(*self.position) + landing_vector + V(0, self.minimal_flight_altitude)) steps.append(Point(*last_step_vector)) return steps def land(self, landscape, callback=None, output=None): """ (generator) Mars lander will land on the given landscape """ # callback function is called at each iteration if not callback: callback = (lambda x: x) # output function is called to print lander's thoughts if not output: output = (lambda x: print(x)) t = 0 while 1: t += 1 output("Hi, this is Mars Lander, and it is {} s.".format(t)) output("I am currently at ({} , {})".format(self.x, self.y)) trajectory = self.compute_trajectory(landscape) output("My trajectory is {}".format(trajectory)) try: target = trajectory[0] output("For now, I try to reach {}".format(target)) except IndexError: output("Right: I'm landing right now") self.power = 4 if self.v_speed > 1 else 3 self.rotation = 0 callback(self) yield t continue target_direction = V.from_to(self.position, target).unit_vector() output("Direction unit vector is {}".format(target_direction)) # current speed vector output("My current speed vector is {}".format(self.speed_vector)) # optimal speed vector if abs(target_direction.dx * self.max_h_landing_speed) >= abs(target_direction.dy * self.max_v_landing_speed): needed_speed_vector = target_direction * self.max_h_landing_speed else: needed_speed_vector = target_direction * self.max_v_landing_speed output("I need a speed like: {}".format(needed_speed_vector)) # compute the new acceleration vector new_acc_vector = needed_speed_vector - self.speed_vector output("I can reach that with that acceleration: {}".format(new_acc_vector)) # look for the closest possible acceleration (p is set to 0.1 instead of 0 in order to give sense to vectors comparison) configs = [(p, r) for p in (0.1, 1, 2, 3, 4) for r in range(-90, 91, 15)] closest_config = min([(p, r, self.compute_acceleration(p, r)) for p, r in configs], \ key=lambda c: Point(*new_acc_vector).distance_to(Point(*c[2]))) power, rotation, new_acc_vector = closest_config if power < 1: power = 0 output("The closest acceleration I could reach is {} ms-2, with power {} and rotation {}".format(new_acc_vector, power, rotation)) # limits the changes if needed if abs(power - self.power) > 1 or abs(rotation - self.rotation) > 15: if power != self.power: power = self.power + int((power - self.power) / abs(power - self.power)) if rotation != self.rotation: rotation = self.rotation + 15 * int((rotation - self.rotation) / abs(rotation - self.rotation)) new_acc_vector = self.compute_acceleration(power, rotation) output("Hmmph, the best I can reach is power {}, rotation {}, for an acceleration of {} ms-2".format(power, rotation, new_acc_vector)) # update the lander attributes self.power = power self.rotation = rotation self.fuel -= power # new position new_x, new_y = V(*self.position) + self.speed_vector self.x, self.y = int(new_x), int(new_y) # new speed self.h_speed, self.v_speed = self.speed_vector + new_acc_vector callback(self) yield t def hyperspace(self): raise NotImplementedError() def distance_to(self, x, y): return math.hypot((x - self.x), (y - self.y)) def angle_to(self, x, y): return math.atan2((y - self.y), (x - self.x)) def send_data(lander): print("{rotation} {power}".format(rotation=lander.rotation, power=lander.power)) # input() def output(msg): print(msg, file=sys.stderr) surface_n = 7 land = [150, 500, 2000, 150, 150, 500, 2000] # land = [3000, 150, 150, 500, 3000, 600, 1000] landscape = Landscape(land) lander = MarsLander() lander.x = 3000 lander.y = 2000 lander.fuel = 1000 gen = lander.land(landscape) for i in range(5): t = next(gen) print(t, lander) input() # lander = MarsLander.from_input( input() ) # landing_point_x, landing_point_y = nearest_landing_zone(lander.x) # target_x, target_y = landing_point_x, landing_point_y # #move_generator = lander.move_to(target_x, target_y, callback=send_data, landing=True, output=output, land_max_elevation=max(land)) # # while True: # target = next_peak(lander.x, landing_point_x) # if target: # if target != (target_x, target_y): # target_x, target_y = target # move_generator = lander.move_to(target_x, target_y + 100, callback=send_data, landing=False) # else: # move_generator = lander.move_to(landing_point_x, landing_point_y, callback=send_data, landing=True) # # t = next(move_generator) # print(t, lander, file=sys.stderr) # # x, y, h_speed, v_speed, fuel, rotate, power = [int(i) for i in input().split()] # #print(x, y, h_speed, v_speed, fuel, rotate, power, file=sys.stderr) # # # # rotate power. rotate is the desired rotation angle. power is the desired thrust power. # # print("{rotation} {power}".format(rotation=lander.rotation, power=lander.power)) # # #input() # get the unused input # #t = next(move)