diff --git a/.idea/agent-aspirateur.iml b/.idea/agent-aspirateur.iml index 786dd58..336d407 100644 --- a/.idea/agent-aspirateur.iml +++ b/.idea/agent-aspirateur.iml @@ -5,4 +5,7 @@ + + \ No newline at end of file diff --git a/.idea/other.xml b/.idea/other.xml new file mode 100644 index 0000000..640fd80 --- /dev/null +++ b/.idea/other.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/hoover-agent/environment.py b/hoover-agent/environment.py index dfc061c..311ee27 100644 --- a/hoover-agent/environment.py +++ b/hoover-agent/environment.py @@ -1,3 +1,7 @@ +from numpy import sqrt, power +import random + + class Thing: """This represents any physical object that can appear in an Environment. You subclass Thing to get the things you want. Each thing can have a @@ -6,10 +10,6 @@ class Thing: def __repr__(self): return '<{}>'.format(getattr(self, '__name__', self.__class__.__name__)) - def is_alive(self): - """Things that are 'alive' should return true.""" - return hasattr(self, 'alive') and self.alive - def show_state(self): """Display the agent's internal state. Subclasses should override.""" print("I don't know how to show_state.") @@ -20,10 +20,46 @@ def display(self, canvas, x, y, width, height): pass -class Agent(Thing): +class Dirt(Thing): pass +class Jewel(Thing): + pass + + +class Agent(Thing): + """An Agent is a subclass of Thing with one required instance attribute + (aka slot), .program, which should hold a function that takes one argument, + the percept, and returns an action. (What counts as a percept or action + will depend on the specific environment in which the agent exists.) + Note that 'program' is a slot, not a method. If it were a method, then the + program could 'cheat' and look at aspects of the agent. It's not supposed + to do that: the program can only look at the percepts. An agent program + that needs a model of the world (and of the agent itself) will have to + build and maintain its own model. There is an optional slot, .performance, + which is a number giving the performance measure of the agent in its + environment.""" + + def __init__(self, program=None): + self.alive = True + self.bump = False + self.holding = [] + self.performance = 0 + if program is None: + print("Can't find a valid program for {}, falling back to default.".format(self.__class__.__name__)) + + def program(percept): + return eval(input('Percept={}; action? '.format(percept))) + + self.program = program + + def can_grab(self, thing): + """Return True if this agent can grab this thing. + Override for appropriate subclasses of Agent and Thing.""" + return False + + class Environment: """Abstract class representing an Environment. 'Real' Environment classes inherit from this. Your Environment will typically need to implement: @@ -35,16 +71,25 @@ class Environment: Each thing has a .location slot, even though some environments may not need this.""" - def __init__(self): + def __init__(self, width=25, height=25): self.things = [] self.agents = [] + self.width = width + self.height = height + + perceptible_distance = 1 + + def things_near(self, location, radius=None): + radius = self.perceptible_distance if radius is None else self.perceptible_distance + return [thing for thing in self.things + if sqrt(power(location[0]-thing.location[0], 2) + power(location[1]-thing.location[1], 2)) <= radius] def thing_classes(self): - return [] # List of classes that can go into environment + return [Dirt, Jewel, Agent] # List of classes that can go into environment def percept(self, agent): - """Return the percept that the agent sees at this point. (Implement this.)""" - raise NotImplementedError + """Return the percept that the agent sees at this point.""" + return 'Dirty' if self.some_things_at(agent.location, Dirt) else 'Clean' def execute_action(self, agent, action): """Change the world to reflect this action. (Implement this.)""" @@ -52,37 +97,48 @@ def execute_action(self, agent, action): def default_location(self, thing): """Default location to place a new thing with unspecified location.""" - return None + location = self.random_location_inbounds() + while self.some_things_at(location, Thing): + location = self.random_location_inbounds() + return location + + def is_inbounds(self, location): + """Checks to make sure that the location is inbounds (within walls if we have walls)""" + x, y = location + return not (x < 0 or x > self.width or y < 0 or y > self.height) + + def random_location_inbounds(self, exclude=None): + """Returns a random location that is inbounds.""" + location = (random.randint(0, self.width), + random.randint(0, self.height)) + if exclude is not None: + while location == exclude: + location = (random.randint(0, self.width), + random.randint(0, self.height)) + return location def exogenous_change(self): """If there is spontaneous change in the world, override this.""" pass - def is_done(self): - """By default, we're done when we can't find a live agent.""" - return NotImplementedError - def step(self): """Run the environment for one time step. If the actions and exogenous changes are independent, this method will do. If there are interactions between them, you'll need to override this method.""" - if not self.is_done(): - actions = [] - for agent in self.agents: - if agent.alive: - actions.append(agent.program(self.percept(agent))) - else: - actions.append("") - for (agent, action) in zip(self.agents, actions): - self.execute_action(agent, action) - self.exogenous_change() + actions = [] + for agent in self.agents: + if agent.alive: + actions.append(agent.program(self.percept(agent))) + else: + actions.append("") + for (agent, action) in zip(self.agents, actions): + self.execute_action(agent, action) + self.exogenous_change() def run(self, steps=1000): """Run the Environment for given number of time steps.""" for step in range(steps): - if self.is_done(): - return self.step() def list_things_at(self, location, tclass=Thing): @@ -112,12 +168,5 @@ def add_thing(self, thing, location=None): def delete_thing(self, thing): """Remove a thing from the environment.""" - try: - self.things.remove(thing) - except ValueError as e: - print(e) - print(" in Environment delete_thing") - print(" Thing to be removed: {} at {}".format(thing, thing.location)) - print(" from list: {}".format([(thing, thing.location) for thing in self.things])) if thing in self.agents: self.agents.remove(thing) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..61dbdfb --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +numpy~=1.21.2 \ No newline at end of file diff --git a/setup.py b/setup.py index 336f6d6..a37c2cd 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ author_email='killian.mahe1@uqac.ca', packages=[ 'pretty-tables', - 'colorama' + 'colorama', + 'numpy', ] )