Advertisement
Guest User

Untitled

a guest
Jan 22nd, 2019
133
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.46 KB | None | 0 0
  1. import pygame as pg
  2. import traceback
  3. from queue import Queue
  4.  
  5. BLACK = (0, 0, 0)
  6. WHITE = (255, 255, 255)
  7. RED = (255, 0, 0)
  8. GREY = (100, 100, 100)
  9. DARKRED = (100, 50, 50)
  10.  
  11. vec = pg.math.Vector2
  12.  
  13. RIGHT = vec(1, 0)
  14.  
  15. # ------------- helper function ----------------------------------------------
  16. def limit(vector, length):
  17.     if vector.length_squared() <= length * length:
  18.         return
  19.     else:
  20.         vector.scale_to_length(length)
  21.  
  22. def remap(n, start1, stop1, start2, stop2):
  23.     # https://p5js.org/reference/#/p5/map
  24.     newval = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2
  25.     if (start2 < stop2):
  26.         return constrain(newval, start2, stop2)
  27.     else:
  28.         return constrain(newval, stop2, start2)    
  29.  
  30. def constrain(n, low, high):
  31.     return max(min(n, high), low)
  32.  
  33.  
  34.  
  35. class Game:
  36.     def __init__(self):
  37.         pg.init()
  38.         self.clock = pg.time.Clock()
  39.         self.screen = pg.display.set_mode((1024, 768))
  40.         self.screen_rect = self.screen.get_rect()        
  41.         self.fps = 60      
  42.         self.all_sprites = pg.sprite.Group()
  43.         self.nodes = pg.sprite.Group()
  44.         self.mobs = pg.sprite.Group()
  45.         self.walls = pg.sprite.Group()      
  46.         # create Nodes to indicate the spawn point and the target of the Mobs
  47.         self.start = Node(self, (40, self.screen_rect.h // 2))
  48.         self.finish = Node(self, (self.screen_rect.w - 40 ,
  49.                                   self.screen_rect.h // 2))
  50.         # variable for placing walls
  51.         self.rect_start = vec(0, 0)
  52.         # text surface for instructions
  53.         self.font = pg.font.SysFont('Arial', 18)
  54.         text = ('MOUSE_1: Place node | MOUSE_2: Hold and drag to place wall | '
  55.                 'MOUSE_3: Delete nodes and walls | M: Spawn Mob | '
  56.                 'R: Restart')
  57.         self.instructions = self.font.render(text, False, WHITE)
  58.        
  59.         self.timer = 0
  60.  
  61.    
  62.     def events(self):
  63.         self.mouse_pos = vec(pg.mouse.get_pos())
  64.         self.mouse_pressed = [0, 0, 0, 0, 0]
  65.         self.mouse_released = [0, 0, 0, 0, 0]
  66.         for event in pg.event.get():
  67.             if event.type == pg.QUIT:
  68.                 self.running = False
  69.             elif event.type == pg.MOUSEBUTTONDOWN:
  70.                 self.mouse_pressed[event.button - 1] = 1
  71.             elif event.type == pg.MOUSEBUTTONUP:
  72.                 self.mouse_released[event.button - 1] = 1
  73.                
  74.             if event.type == pg.KEYDOWN:
  75.                 if event.key == pg.K_r:
  76.                     # clear all sprites
  77.                     self.all_sprites.empty()
  78.                     self.nodes.empty()
  79.                     self.mobs.empty()
  80.                     self.walls.empty()
  81.                     self.start = Node(self, (40, 300))
  82.                     self.finish = Node(self, (960, 300))
  83.                     for node in self.nodes:
  84.                         node.find_neighbors()
  85.                
  86.                 elif event.key == pg.K_m:
  87.                     # spawns a Mob when pressing the M key
  88.                     Mob(self, self.start.position)
  89.  
  90.    
  91.     def update(self):  
  92.         pg.display.set_caption(str(round(self.clock.get_fps(), 2)))
  93.         self.all_sprites.update()
  94.        
  95.         if self.mouse_pressed[0]:
  96.             Node(self, self.mouse_pos)
  97.             # call find_neighbors only when something has changed
  98.             # to save on performance
  99.             for node in self.nodes:
  100.                 node.find_neighbors()
  101.                
  102.         if self.mouse_pressed[1]:
  103.             # begin a rectangle for placing a wall
  104.             self.rect_start = self.mouse_pos
  105.            
  106.         if self.mouse_released[1]:
  107.             # calculate the topleft, width and height of the Wall
  108.             # and place it
  109.             w = self.mouse_pos.x - self.rect_start.x
  110.             h = self.mouse_pos.y - self.rect_start.y
  111.             if w > 0 and h > 0:
  112.                 x, y = self.rect_start
  113.             elif w > 0 and h < 0:
  114.                 x = self.mouse_pos.x - w
  115.                 y = self.mouse_pos.y
  116.             elif w < 0 and h > 0:
  117.                 x = self.mouse_pos.x
  118.                 y = self.mouse_pos.y - h
  119.             else:
  120.                 x, y = self.mouse_pos
  121.             if abs(w) > 2 and abs(h) > 2:
  122.                 Wall(self, (x, y), (abs(w), abs(h)))                
  123.             self.rect_start = None
  124.            
  125.             for node in self.nodes:
  126.                 node.find_neighbors()
  127.              
  128.         if self.mouse_pressed[2]:
  129.             # remove objects with mouse right
  130.             for node in self.nodes:
  131.                 node.find_neighbors()
  132.                 if node.rect.collidepoint(self.mouse_pos):
  133.                     node.kill()
  134.                    
  135.         self.timer += 1
  136.         if self.timer >= 60:
  137.             self.timer = 0
  138.             Mob(self, self.start.position)
  139.        
  140.        
  141.    
  142.     def draw(self):
  143.         self.screen.fill(BLACK)
  144.         # draw lines between connected nodes
  145.         self.draw_connections()
  146.         self.all_sprites.draw(self.screen)
  147.         # draw a rect if the player is holding the middle mouse button
  148.         if self.rect_start:
  149.             w = self.mouse_pos.x - self.rect_start.x
  150.             h = self.mouse_pos.y - self.rect_start.y      
  151.             pg.draw.rect(self.screen, WHITE, pg.Rect(self.rect_start, (w, h)), 2)
  152.         # highlight neibors in red
  153.         for node in self.nodes:
  154.             node.draw_neighbors()
  155.         # draw the path of each mob
  156.         for mob in self.mobs:
  157.             mob.draw_path(self.screen)
  158.         # draw instructions text on the top
  159.         rect = self.instructions.get_rect()
  160.         rect.centerx = self.screen_rect.centerx
  161.         rect.y = 4
  162.         self.screen.blit(self.instructions, rect)
  163.  
  164.         pg.display.update()
  165.    
  166.    
  167.     def draw_connections(self):
  168.         for node in self.nodes:
  169.             for n in node.neighbors:
  170.                 start = node.position
  171.                 end = n.position
  172.                 pg.draw.line(self.screen, GREY, start, end, 2)
  173.    
  174.  
  175.     def breadth_first_search(self, start, goal):
  176.         # https://www.redblobgames.com/pathfinding/a-star/introduction.html
  177.         frontier = Queue()
  178.         frontier.put(start)
  179.         came_from = {}
  180.         came_from[start] = None
  181.         # visit all nodes
  182.         while not frontier.empty():
  183.             current = frontier.get()
  184.             for next in current.find_neighbors():
  185.                 if next not in came_from:
  186.                     frontier.put(next)
  187.                     came_from[next] = current
  188.         # get path with the fewest nodes
  189.         current = goal
  190.         path = []
  191.         while current != start:
  192.             path.append(current)
  193.             current = came_from[current]
  194.         path.append(start)
  195.         path.reverse()
  196.         return path
  197.        
  198.              
  199.     def run(self):
  200.         self.running = True
  201.         while self.running:
  202.             self.clock.tick(self.fps)
  203.             self.events()        
  204.             self.update()
  205.             self.draw()
  206.        
  207.         pg.quit()
  208.  
  209.  
  210.  
  211. class Mob(pg.sprite.Sprite):
  212.     def __init__(self, game, position):
  213.         super().__init__(game.all_sprites, game.mobs)
  214.         self.game = game
  215.         self.image = pg.Surface((20, 20))
  216.         self.image.fill(RED)
  217.         self.rect = self.image.get_rect()
  218.        
  219.         self.acc = vec()
  220.         self.vel = vec()
  221.         self.pos = vec(position)
  222.         self.rect.center = self.pos
  223.         try:
  224.             self.path = self.game.breadth_first_search(self.game.start,
  225.                                                        self.game.finish)
  226.         except:
  227.             # if no path can be found, kill the mob
  228.             self.kill()
  229.             return
  230.         self.current_target = 0
  231.         self.target = self.path[self.current_target]
  232.         self.speed = 6
  233.         self.friction = 0.9
  234.        
  235.        
  236.        
  237.    
  238.     def update(self):
  239.         # apply motion
  240.         self.acc += self.arrive(self.target.position)
  241.         self.vel += self.acc * self.speed
  242.         self.acc *= 0
  243.         self.vel *= self.friction
  244.         self.pos += self.vel
  245.         self.rect.center = self.pos
  246.        
  247.         # if target is reached, set next target in path
  248.         d = self.target.position - self.pos # distance vector to target
  249.         if d.length() < self.speed:
  250.             self.current_target += 1
  251.             try:
  252.                 self.target = self.path[self.current_target]
  253.             except:
  254.                 # when there is not target left, remove the Mob
  255.                 self.kill()
  256.        
  257.    
  258.     def arrive(self, target):
  259.         # make the mob move to a target position
  260.         desired = target - self.pos
  261.         d = desired.length()
  262.         if d > 0:
  263.             desired = desired.normalize()
  264.         desired *= self.speed
  265.         # calculate steering force
  266.         steering = desired - self.vel
  267.         limit(steering, 1)
  268.        
  269.         return steering
  270.    
  271.    
  272.     def draw_path(self, screen):
  273.         if len(self.path) > 1:
  274.             lines = [node.position for node in self.path]
  275.             pg.draw.lines(screen, RED, False, lines)
  276.  
  277.  
  278.  
  279. class Node(pg.sprite.Sprite):
  280.     def __init__(self, game, position):
  281.         super().__init__(game.all_sprites, game.nodes)
  282.         self.game = game
  283.         self.image = pg.Surface((12, 12))
  284.         self.image.fill(WHITE)
  285.         self.rect = self.image.get_rect()
  286.         self.rect.center = position
  287.         self.position = vec(position)
  288.         self.neighbors = []
  289.    
  290.    
  291.     def __repr__(self):
  292.         return str(self.position)
  293.        
  294.    
  295.     def find_neighbors(self):
  296.         # cast a ray to each other node and if it doesn't intersect a wall
  297.         # add that node to neighbors
  298.         self.neighbors.clear()
  299.         for node in self.game.nodes:
  300.             if node != self:
  301.                 dist = Line(self.position, node.position)
  302.                 intersects = False
  303.                 for wall in self.game.walls:
  304.                     if dist.intersects_rect(wall.rect):
  305.                         intersects = True
  306.                 if not intersects:
  307.                     self.neighbors.append(node)
  308.         return self.neighbors
  309.    
  310.    
  311.     def draw_neighbors(self):
  312.         for node in self.neighbors:
  313.             if self.rect.collidepoint(self.game.mouse_pos):
  314.                 pg.draw.rect(self.game.screen, RED, node.rect)
  315.                
  316.                
  317.    
  318. class Wall(pg.sprite.Sprite):
  319.     def __init__(self, game, position, size):
  320.         super().__init__(game.all_sprites, game.walls)
  321.         self.game = game
  322.         self.image = pg.Surface(size)
  323.         self.image.fill(DARKRED)
  324.         self.rect = self.image.get_rect()
  325.         self.rect.topleft = position
  326.         self.position = vec(position)
  327.        
  328.         # delete any nodes that collide with this wall
  329.         for node in self.game.nodes:
  330.             if self.rect.colliderect(node.rect):
  331.                 node.kill()
  332.    
  333.    
  334.     def update(self):
  335.         # check if the player clicks right while hovering over this wall
  336.         if (self.game.mouse_pressed[2] and
  337.             self.rect.collidepoint(self.game.mouse_pos)):
  338.             self.kill()
  339.  
  340.  
  341. class Line:
  342.     # class that represents a line from one point to another
  343.     def __init__(self, start, end):
  344.         self.start = vec(start)
  345.         self.end = vec(end)
  346.    
  347.     def draw(self, screen, color=WHITE, width=1):
  348.         pg.draw.line(screen, color, self.start, self.end, width)
  349.        
  350.    
  351.     def intersects_line(self, other):
  352.         # checks if two Line objects intersect
  353.         #http://www.jeffreythompson.org/collision-detection/line-rect.php
  354.         # calculate denominators for uA and uB
  355.         denA = ((other.end.y - other.start.y) * (self.end.x - self.start.x) -
  356.                 (other.end.x - other.start.x) * (self.end.y - self.start.y))
  357.         denB = ((other.end.y - other.start.y) * (self.end.x - self.start.x) -
  358.                 (other.end.x - other.start.x) * (self.end.y - self.start.y))
  359.         if denA == 0 or denB == 0:
  360.             # if any denominator is 0, the lines are parallel and don't intersect
  361.             return False
  362.         else:
  363.             # calculate numerators for uA and uB
  364.             numA = ((other.end.x - other.start.x) * (self.start.y - other.start.y) -
  365.                     (other.end.y - other.start.y) * (self.start.x - other.start.x))
  366.             numB = ((self.end.x - self.start.x) * (self.start.y - other.start.y) -
  367.                     (self.end.y - self.start.y) * (self.start.x - other.start.x))
  368.             uA = numA / denA
  369.             uB = numB / denB
  370.             return (uA >= 0 and uA <= 1 and uB >= 0 and uB <= 1)
  371.  
  372.    
  373.     def get_lines_from_rect(self, rect):
  374.         # returns a list with all 4 sides of a given rect as Line objects
  375.         l1 = Line(rect.topleft, rect.topright)
  376.         l2 = Line(rect.topright, rect.bottomright)
  377.         l3 = Line(rect.bottomright, rect.bottomleft)
  378.         l4 = Line(rect.bottomleft, rect.topleft)
  379.         return [l1, l2, l3, l4]
  380.  
  381.    
  382.     def intersects_rect(self, rect):
  383.         # checks if this line intersects any lines from a given rect
  384.         lines = self.get_lines_from_rect(rect)
  385.         for line in lines:
  386.             if self.intersects_line(line):
  387.                 return True
  388.         return False
  389.    
  390.    
  391.        
  392. if __name__ == '__main__':
  393.     try:
  394.         g = Game()
  395.         g.run()
  396.     except:
  397.         traceback.print_exc()
  398.         pg.quit()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement