| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- '''
- 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 "<Vector ({}, {})>".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 "<Point ({}, {})>".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 "<MarsLander: {}>".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)
|