Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import math
- import pymunk
- from pymunk import Vec2d
- import gameobjects
- from collections import defaultdict, deque
- # NOTE: use only 'map0' during development!
- """
- # TODO:
- flag on tank = False när den dör
- """
- MIN_ANGLE_DIF = math.radians(4) # 3 degrees, a bit more than we can turn each tick
- TILE_SIZE = 1
- def angle_between_vectors(vec1, vec2):
- """ Since Vec2d operates in a cartesian coordinate space we have to
- convert the resulting vector to get the correct angle for our space.
- """
- vec = vec1 - vec2
- vec = vec.perpendicular()
- return vec.angle
- def periodic_difference_of_angles(angle1, angle2):
- return (angle1% (2*math.pi)) - (angle2% (2*math.pi))
- class Ai:
- """ A simple ai that finds the shortest path to the target using
- a breadth first search. Also capable of shooting other tanks and or wooden
- boxes. """
- def __init__(self, tank, game_objects_list, tanks_list, space, currentmap):
- self.tank = tank
- self.game_objects_list = game_objects_list
- self.tanks_list = tanks_list
- self.space = space
- self.currentmap = currentmap
- self.flag = None
- self.MAX_X = currentmap.width - 1
- self.MAX_Y = currentmap.height - 1
- self.last_distance = 999
- self.path = deque()
- self.move_cycle = self.move_cycle_gen()
- self.update_grid_pos()
- def update_grid_pos(self):
- """ This should only be called in the beginning, or at the end of a move_cycle. """
- self.grid_pos = self.get_tile_of_position(self.tank.body.position)
- def maybe_shoot(self):
- """ Makes a raycast query in front of the tank. If another tank
- or a wooden box is found, then we shoot.
- """
- ray = self.space.segment_query_first((self.tank.body.position[0] - 0.6 * math.sin(self.tank.body.angle), self.tank.body.position[1] + 0.6 * math.cos(self.tank.body.angle)), (self.tank.body.position[0] - 10*math.sin(self.tank.body.angle),self.tank.body.position[1] + 10*math.cos(self.tank.body.angle)) , 0, pymunk.ShapeFilter())
- if ray != None:
- try:
- if hasattr(ray, 'shape'):
- if isinstance(ray.shape.parent, gameobjects.Tank):
- bullet = self.tank.shoot(self.space)
- if bullet != None:
- self.game_objects_list.append(bullet)
- elif isinstance(ray.shape.parent, gameobjects.Box):
- if ray.shape.parent.boxmodel.destructable == True:
- bullet = self.tank.shoot(self.space)
- if bullet != None:
- self.game_objects_list.append(bullet)
- except:
- pass
- def decide(self):
- """ Main decision function that gets called on every tick of the game. """
- next(self.move_cycle)
- self.maybe_shoot()
- def correct_angle(self, tank_angle, target_angle):
- angle_dif = periodic_difference_of_angles(target_angle, tank_angle)
- if abs(angle_dif) <= MIN_ANGLE_DIF:
- self.tank.stop_turning()
- return True
- else:
- return False
- def correct_position(self, target_pos, last_distance):
- tank_pos = Vec2d(self.tank.body.position)
- current_distance = target_pos.get_distance(tank_pos)
- self.last_distance = current_distance
- if last_distance <= current_distance+0.008:
- return True
- else:
- return False
- def turn(self, tank_angle, target_angle):
- angle_dif = periodic_difference_of_angles(tank_angle, target_angle)
- #tank_angle = tank_angle % 2*(math.pi)
- if (angle_dif+2*math.pi)%2*math.pi >= math.pi and abs(angle_dif) > MIN_ANGLE_DIF:
- self.tank.stop_moving()
- self.tank.turn_left()
- elif (angle_dif+2*math.pi)%2*math.pi < math.pi and abs(angle_dif) > MIN_ANGLE_DIF:
- self.tank.stop_moving()
- self.tank.turn_right()
- def move_cycle_gen(self):
- """ A generator that iteratively goes through all the required steps
- to move to our goal.
- """
- while True:
- path = self.find_shortest_path()
- if not path:
- yield
- continue
- target_pos = path.popleft()
- target_pos += Vec2d(0.5, 0.5)
- tank_pos = Vec2d(self.tank.body.position)
- yield
- tank_angle = self.tank.body.angle
- target_angle = angle_between_vectors(tank_pos, target_pos)
- angle_dif = periodic_difference_of_angles(tank_angle, target_angle)
- last_distance = tank_pos.get_distance(target_pos)
- self.turn(tank_angle, target_angle)
- while not self.correct_angle(tank_angle, target_angle):
- tank_angle = self.tank.body.angle
- target_angle = angle_between_vectors(tank_pos, target_pos)
- yield
- self.tank.accelerate()
- while not self.correct_position(target_pos, self.last_distance):
- yield
- self.update_grid_pos()
- yield
- def find_shortest_path(self):
- """ A simple Breadth First Search using integer coordinates as our nodes.
- Edges are calculated as we go, using an external function. """
- # To be implemented
- self.update_grid_pos()
- searchtree = {}
- queue = deque()
- shortest_path = []
- visited = set()
- starting_position = self.grid_pos
- target = self.get_target_tile()
- queue.append(starting_position)
- visited.add(starting_position.int_tuple)
- while queue:
- node = Vec2d(queue.popleft())
- if node == target.int_tuple:
- goal_node = node.int_tuple
- break
- for neighbor in self.get_tile_neighbors(node):
- neighbor = neighbor.int_tuple
- if neighbor not in visited:
- queue.append(neighbor)
- visited.add(neighbor)
- searchtree[neighbor] = node.int_tuple
- key = goal_node
- while key != self.grid_pos.int_tuple:
- shortest_path.append(Vec2d(key))
- parent_node = searchtree[key]
- key = parent_node
- shortest_path = shortest_path[::-1]
- return deque(shortest_path)
- def get_target_tile(self):
- """ Returns position of the flag if we don't have it. If we do have the flag,
- return the position of our home base.
- """
- if self.tank.flag != None:
- x, y = self.tank.start_position
- else:
- self.get_flag() # Ensure that we have initialized it.
- x, y = self.flag.x, self.flag.y
- return Vec2d(int(x), int(y))
- def get_flag(self):
- """ This has to be called to get the flag, since we don't know
- where it is when the Ai object is initialized.
- """
- if self.flag == None:
- # Find the flag in the game objects list
- for obj in self.game_objects_list:
- if isinstance(obj, gameobjects.Flag):
- self.flag = obj
- break
- return self.flag
- def get_tile_of_position(self, position_vector):
- """ Converts and returns the float position of our tank to an integer position. """
- x, y = position_vector
- return Vec2d(int(x), int(y))
- def get_tile_neighbors(self, coord_vec):
- """ Returns all bordering grid squares of the input coordinate.
- A bordering square is only considered accessible if it is grass
- or a wooden box.
- """
- #print(coord_vec)
- left = Vec2d(coord_vec[0] - 1 , coord_vec[1])
- right = Vec2d(coord_vec[0] + 1, coord_vec[1])
- down = Vec2d(coord_vec[0], coord_vec[1] + 1)
- up = Vec2d(coord_vec[0], coord_vec[1] - 1)
- neighbors = [left, right, down, up] # Find the coordinates of the tiles' four neighbors
- #print(neighbors)
- return filter(self.filter_tile_neighbors, neighbors)
- def filter_tile_neighbors (self, coord):
- coord = coord.int_tuple
- if coord[1] <= self.MAX_Y and coord[0] <= self.MAX_X and coord[1] >= 0 and coord[0] >= 0 and (self.currentmap.boxAt(coord[0], coord[1]) == 0 or self.currentmap.boxAt(coord[0], coord[1]) == 2):
- return True
- return False
- SimpleAi = Ai # Legacy
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement