Exemplo n.º 1
0
class Main():
    def __init__(self):
        # Initialise variables and randomly generate ane nvironment
        self.initialize_variables()
        self.generate_world()
        # Update display
        self.update_display = True

        # main loop
        while True:
            # Check for inputs
            self.check_inputs()
            # If not paused
            if not self.paused:
                # If a run is ongoing
                if self.running:
                    # Different modes
                    if self.training:
                        self.training_mode()
                    else:
                        self.free_mode()
                # Update environment objects positions
                self.move_environment()
                # Update the display if the user chooses too
                # Program runs faster when it is off
                if self.update_display:
                    self.draw_sprites()

    def initialize_variables(self):
        # define display surface
        self.dimensions = (1080, 600)
        # Initial parameters
        self.subsumption = True
        self.episode_num = 0
        self.extra = False
        self.agent_num = 0
        self.placing_object = False
        self.rotate_unplaced = 0
        self.continuous = True
        self.draw_goal = False
        self.wolf = True
        self.randomness = 5
        self.reference = 20
        self.draw_scaleable = False
        self.display = [False, False, False]
        self.training = False
        self.running = False
        self.custom_build = False
        self.paused = False
        self.agents_to_be_added = []

        # Backgrounds colour
        self.background = (0, 153, 255, 221)

        # Keeps track of the winrate across training stages
        self.win_counter = 0
        self.network_collection = NetworkCollection()
        # Place window in the center of the screen
        os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (150, 80)
        # initialise display
        pygame.init()
        self.clock = pygame.time.Clock()
        self.canvas = pygame.display.set_mode(self.dimensions)
        pygame.display.set_caption("Intelligent Agent Structures")
        # Create a font used to write on the screen
        pygame.font.init()
        self.font = pygame.font.SysFont('arial', 12)
        self.font.set_bold(True)

        # Get the image resources for the world
        self.pointers = [None, None]
        self.pointers[0] = pygame.image.load(
            "image_resources/pointer_alt.png").convert_alpha()
        self.pointers[1] = pygame.image.load(
            "image_resources/pointer.png").convert_alpha()

        # array containing the environments current blocks and agents
        self.blocks, self.agents = [], []
        # Adds agents into the world
        self.floor = Object(0, (0, 400))
        self.floor.load_image("image_resources/flat_floor.png")

        # File paths for the block images
        self.block_images = [
            "300x200", "200x600", "400x400", "200x200", "goal", "goal_flipped"
        ]

        # UI Tabs for environment modification, output log and controls
        self.tabs = []
        self.tabs.append(Options(0, self))
        self.tabs.append(Log(1, self.font, self))
        self.tabs.append(Tab(2))

        # Static agents used in the final training stage
        self.scaleable_agents = []
        preset_rotations = [
            44, 0, 270, 90, 90, 90, 0, 0, 0, 90, 90, 90, 0, 0, 0
        ]
        self.scaleable_agents.append(
            Agent((582, 339), self.network_collection, 0, None, 25, False,
                  self.randomness, self.subsumption))
        self.scaleable_agents.append(
            Agent((667, 339), self.network_collection, 0, None, 25, False,
                  self.randomness, self.subsumption))
        self.scaleable_agents.append(
            Agent((667, 304), self.network_collection, 0, None, 25, False,
                  self.randomness, self.subsumption))
        for i in range(3):
            self.scaleable_agents[i].parts.set_rotations(preset_rotations)
        # Rounds won/lost
        self.won = 0
        self.lost = 0

    # Check computer inputs
    def check_inputs(self):
        # Event Listener
        for event in pygame.event.get():
            # On quit event quit game
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == KEYDOWN:
                # Start run
                if event.key == K_RETURN:
                    self.running = True
                # End run / Reset environment
                elif event.key == K_ESCAPE:
                    self.reset_environment()
                # Rotate block clockwise and anti clockwise
                elif event.key == K_q:
                    self.rotate_unplaced += 1
                elif event.key == K_e:
                    self.rotate_unplaced += -1
                # Update display
                elif event.key == K_u:
                    self.update_display = not self.update_display
                # Play / Pause
                elif event.key == K_p:
                    self.paused = not self.paused
            elif event.type == KEYUP:
                # Stop rotating block
                if event.key == K_e:
                    self.rotate_unplaced += 1
                elif event.key == K_q:
                    self.rotate_unplaced += -1
            # On mouse click check which button was pressed
            elif event.type == pygame.MOUSEBUTTONDOWN and not self.paused:
                self.pos = pygame.mouse.get_pos()
                # If an object is being held by the user place it
                if self.placing_object:
                    self.place_object()
                # Check buttons
                for i in range(3):
                    self.tabs[i].click(self.pos)

    # Draw sprites
    def draw_sprites(self):
        # Draw background
        self.canvas.fill(self.background)
        # Draw the goal if there is one
        if self.draw_goal:
            self.canvas.blit(self.goal.get_image(), self.goal.get_position())
        # Draw sensors if user chooses
        if self.display[1]:
            for i in range(len(self.agents)):
                self.agents[i].sensors.run(self.canvas)
        # Draw any blocks within the environment including any the user is holding
        if self.placing_object:
            self.canvas.blit(self.unplaced.get_image(),
                             self.unplaced.get_position())
        for i in range(len(self.blocks)):
            self.canvas.blit(self.blocks[i].get_image(),
                             self.blocks[i].get_position())
        # Draw the agents used in the final training stage
        if self.draw_scaleable:
            for i in range(len(self.scaleable_agents)):
                self.scaleable_agents[i].parts.run(self.canvas)
        # Draw the agents
        for i in range(len(self.agents)):
            self.agents[i].parts.run(self.canvas)
        # Draw the floor
        self.canvas.blit(self.floor.get_image(), self.floor.get_position())
        # Draw the agents collision points with the environment if the request it
        if self.display[0]:
            for i in range(len(self.agents)):
                markers = self.agents[i].get_box()
                for j in range(len(markers)):
                    self.canvas.blit(self.pointers[1],
                                     (int(markers[j][0]), int(markers[j][1])))
        # Draw the agents center of gravity if the request it
        if self.display[2]:
            for i in range(len(self.agents)):
                cog = self.agents[i].cog
                self.canvas.blit(self.pointers[0], (int(cog[0]), int(cog[1])))
        # Draw tabs
        for i in range(3):
            self.tabs[i].run(self.canvas)
        pygame.display.update()
        # Max FPS
        self.clock.tick(120)

    # Place an object the user is holding
    def place_object(self):
        # It must overlap with the environment (No freefloating structures)
        if (self.check_environmental_overlap()):
            # Place new goal
            if self.unplaced_goal:
                self.goal = self.unplaced
                self.draw_goal = True
            # Place block
            else:
                self.blocks.append(self.unplaced)
        self.placing_object = False

    # Check if a block is overlapping with the environment (No free floating structures)
    def check_environmental_overlap(self, goal=False):
        # Compare either the goal or the block the user is currently holding
        checking = self.unplaced
        if goal:
            checking = self.goal
        # Compare against every block including the floor
        for k in range(len(self.blocks) + 1):
            comparison = None
            if k == len(self.blocks):
                comparison = [self.floor.get_position(), self.floor.get_mask()]
            else:
                comparison = [
                    self.blocks[k].get_position(), self.blocks[k].get_mask()
                ]

            # Find the position difference between the agent part and the external object
            offset = (int(comparison[0][0] - checking.get_position()[0]),
                      int(comparison[0][1] - checking.get_position()[1]))
            result = checking.get_mask().overlap(comparison[1], offset)
            # If there was a collision return the collision position
            if result:
                return result[1]
        # Else return false
        return False

    # Randomly generate a new environment
    def generate_world(self):
        # Reset environment
        self.blocks = []
        self.agents = []
        self.agent_num = 0
        # Randomly place 3 blocks.
        while len(self.blocks) < 3:
            self.unplaced = Object(0, (randint(500, 800), randint(0, 200)))
            self.unplaced.load_image("image_resources/" +
                                     self.block_images[randint(0, 3)] + ".png")
            self.unplaced.rotate(randint(0, 180))
            # Only keep the block if it touches the environment
            if self.check_environmental_overlap():
                self.blocks.append(self.unplaced)
        # Place goal
        self.set_goal()

    # Move an item in the environment. Either what the user it holding or a tab
    def move_environment(self):
        # User holding item
        if self.placing_object:
            self.pos = pygame.mouse.get_pos()
            if self.unplaced_goal:
                self.unplaced.set_position(
                    (self.pos[0] - 25, self.pos[1] - 25))
            else:
                self.unplaced.set_position(
                    (self.pos[0] - 200, self.pos[1] - 200))
                self.unplaced.rotate(self.rotate_unplaced)
        # Update tabs
        for i in range(3):
            self.tabs[i].move()

    # Automatically place goal in the environment
    def set_goal(self):
        # Goal is shapped like a flag. Keep trying to place goal until the attaches to the ground by the flag pole
        for i in range(10):
            # Randomly position
            x_pos = randint(850, 900)
            self.goal = Object(0, (x_pos, 0))
            # Pick a direction for the goal to face. When placing on a slope means it can attach to the ground by the pole
            flipped = False
            if randint(0, 100) > 50:
                self.goal.load_image("image_resources/goal.png")
            else:
                self.goal.load_image("image_resources/goal_flipped.png")
                flipped = True
            counter = 0
            # Start the goal in the air. Keep lowering until it touches the ground
            while (not self.check_environmental_overlap(True)):
                counter += 1
                self.goal.set_position((x_pos, counter))
            # Lower the goal by 2 pixels to plant it firmly in the ground
            self.goal.set_position((x_pos, counter + 2))
            # If planted by the flag pole break otherwise try a maximum of 10 times
            if self.check_environmental_overlap(True) > 40:
                break
        self.draw_goal = True

    # Reset environment
    def reset_environment(self, clear_world=False):
        # Reset variables
        self.running = False
        self.placing_object = False
        self.network_collection.reset_training()
        # Clear blocks and agents
        self.agents = []
        self.agent_num = 0
        self.episode_num = 0
        self.draw_scaleable = False
        if not clear_world:
            self.blocks = []
            self.draw_goal = False
        # If not custom built environments generate a new one
        if not self.custom_build:
            self.generate_world()
        self.tabs[1].write_message("Environment Reset")

    # When the user requests a block give them one
    def world_building(self, object_num):
        if not self.running and (object_num < 4 or self.draw_goal == False):
            if self.custom_build or object_num > 3:
                self.placing_object = True
                # Centered so cursor is in the middle of the image
                self.unplaced = Object(0,
                                       (self.pos[0] - 200, self.pos[1] - 200))
                self.unplaced.load_image("image_resources/" +
                                         self.block_images[object_num] +
                                         ".png")
                self.unplaced_goal = (object_num > 3)

    # Training mode. Here the user performs several subtasks to train the neural network
    def training_mode(self):
        # If no agents exist create one
        if len(self.agents) == 0:
            if self.reference > 0:
                self.create_agent(290, True, True)
            else:
                self.create_agent(290, True, False)

        # Get agent
        agent = self.agents[0]
        # Get collection of neural networks
        network_collection = self.network_collection
        # Move agent
        agent.move()

        # If the agent finishes its run reset environment
        if agent.restart:
            # Did the agent win
            if agent.won:
                self.won += 1
                if self.extra:
                    self.tabs[1].write_message("WON")
            else:
                self.lost += 1
                if self.extra:
                    self.tabs[1].write_message("LOST")

            # Run the first 2 training stages until the networks have 20000 pieces of data.
            # Run the last 2 training stages until the networks have 50000 pieces of data.
            comparator = 50000
            if network_collection.get_turn() < 2:
                comparator = 20000

            difference = (network_collection.get_length() -
                          (comparator * self.reference / 100))

            # If the network has enough data
            if network_collection.get_length() > comparator:
                # Move to the next step
                self.tabs[1].write_message("Training Stage " +
                                           str(network_collection.get_turn() +
                                               1) + "/4 Complete")
                network_collection.increment_turn()
                # Generate new environment
                self.generate_world()
                # Average of the averages from each stage of training
                self.win_counter += (self.won / (self.won + self.lost)) / 4
                # Finish episode
                if network_collection.get_turn() == 0:
                    self.tabs[1].write_message("Episode " +
                                               str(self.episode_num + 1) +
                                               " Finished")
                    self.tabs[1].write_message(
                        "Training, This may take a while")
                    self.draw_scaleable = False
                    self.draw_sprites()
                    # If using Win or Learn Fast update the learning rate
                    learn_rate = network_collection.get_learn_rate()
                    if self.wolf:
                        learn_rate = 0.1 - (self.win_counter * 0.099)
                        network_collection.update_learn_rate(learn_rate)
                    # Finish episode and save network
                    network_collection.finish_episode()
                    network_collection.save_checkpoint()
                    self.tabs[1].write_message("Network Trained With LR: " +
                                               str("%.3f" % learn_rate))

                    # Reset variables
                    self.episode_num += 1
                    self.win_counter = 0
                    preset = False

                    # End run
                    self.run_ended()

                # Empty environment except on the 3rd stage
                elif network_collection.get_turn() == 3:
                    self.blocks = []
                    self.draw_goal = False
                # Reset win/loss count
                self.won = 0
                self.lost = 0
            elif self.extra:
                self.tabs[1].write_message(
                    str(network_collection.get_length()) + " / " +
                    str(comparator))

            # Reset agent
            self.agents = []
            self.create_agent(290, True, (network_collection.get_length() <
                                          (comparator * self.reference / 100)))

            # On the 3rd training step spawn static training agents
            if network_collection.get_turn() == 3:
                for j in range(len(self.scaleable_agents)):
                    self.agents[self.agent_num].add_other_agent(
                        self.scaleable_agents[j])
                self.draw_scaleable = True

    # Free mode. Environment are built and agents attempt to work together to scale them
    def free_mode(self):
        # If no agents currently exist create one
        if len(self.agents) == 0:
            # If there is no goal make one
            if not self.draw_goal:
                self.set_goal()
            self.create_agent(390, False)

        # Move all the agents
        for i in range(self.agent_num + 1):
            self.agents[i].move()
            # Once an agent is confirmed to be finished add its mask to the next agent
            if self.agents[i].finished_completely:
                self.agents[i].finished_completely = False
                self.agents[self.agent_num].add_other_agent(self.agents[i])
        # If an agent requests another then spawn another agent
        if self.agents[self.agent_num].finished == 1:
            self.agents_to_be_added = [self.agent_num]
            self.agent_num += 1
            self.create_agent(100, False)
        # If the agent declares a run finished, end run
        elif self.agents[self.agent_num].finished == -1:
            if self.agents[self.agent_num].won:
                self.tabs[1].write_message("Run Finished Successfully")
            else:
                self.tabs[1].write_message("Run Failed")
            self.run_ended()

    # Run ended.
    def run_ended(self):
        # If it's in continuous create a new run otherwise reset environment and wait for user input
        if self.continuous:
            self.agents = []
            # If the environment is custom keep it otherwise reset the environment
            if self.custom_build:
                self.agent_num = 0
            else:
                self.reset_environment()
                self.running = True
        else:
            self.reset_environment(self.custom_build)

    # Create a new agent
    def create_agent(self, x_pos, training, reference=False):
        # Append to agent array
        if self.training:
            # Choose if the run is a reference of the neural network
            if self.extra:
                if reference:
                    self.tabs[1].write_message("Reference")
                else:
                    self.tabs[1].write_message("Neural Network")
            self.agents.append(
                Agent((x_pos, 200), self.network_collection, 0, None, 25, True,
                      self.randomness, reference))
        else:
            self.agents.append(
                Agent((x_pos, 200), self.network_collection, 0, self.goal, 25,
                      False, self.randomness, self.subsumption))
        if self.extra:
            self.tabs[1].write_message("New Agent Created")
        # Inform agent of the other agents positions
        for j in range(len(self.agents) - 2):
            self.agents[self.agent_num].add_other_agent(self.agents[j])
        self.agents[self.agent_num].add_object(
            (self.floor.get_mask(), self.floor.get_position()[0],
             self.floor.get_position()[1]))
        # Inform agent of the environmental objects positions
        for j in range(len(self.blocks)):
            self.agents[self.agent_num].add_object(
                (self.blocks[j].get_mask(), self.blocks[j].get_position()[0],
                 self.blocks[j].get_position()[1]))