From a17d6f38efcb0620e9c0b3eb4e2e6606c32396cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Killian=20Mah=C3=A9?= Date: Mon, 27 Sep 2021 15:29:32 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20graphical=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/agent-aspirateur.iml | 2 +- .idea/misc.xml | 2 +- hoover-agent/__main__.py | 3 - hoover-agent/environment.py | 172 --------------------- requirements.txt | 1 + setup.py | 4 +- {hoover-agent => vacuum-agent}/__init__.py | 0 vacuum-agent/__main__.py | 116 ++++++++++++++ vacuum-agent/agent.py | 136 ++++++++++++++++ {hoover-agent => vacuum-agent}/logic.py | 0 10 files changed, 256 insertions(+), 180 deletions(-) delete mode 100644 hoover-agent/__main__.py delete mode 100644 hoover-agent/environment.py create mode 100644 requirements.txt rename {hoover-agent => vacuum-agent}/__init__.py (100%) create mode 100644 vacuum-agent/__main__.py create mode 100644 vacuum-agent/agent.py rename {hoover-agent => vacuum-agent}/logic.py (100%) diff --git a/.idea/agent-aspirateur.iml b/.idea/agent-aspirateur.iml index 9095021..c025632 100644 --- a/.idea/agent-aspirateur.iml +++ b/.idea/agent-aspirateur.iml @@ -4,7 +4,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index 13fecf3..ae34238 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/hoover-agent/__main__.py b/hoover-agent/__main__.py deleted file mode 100644 index 40ea1e0..0000000 --- a/hoover-agent/__main__.py +++ /dev/null @@ -1,3 +0,0 @@ -from environment import Agent, Environment - -print("Hello world!") diff --git a/hoover-agent/environment.py b/hoover-agent/environment.py deleted file mode 100644 index fe7c9f7..0000000 --- a/hoover-agent/environment.py +++ /dev/null @@ -1,172 +0,0 @@ -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 - .__name__ slot (used for output only).""" - - def __repr__(self): - return '<{}>'.format(getattr(self, '__name__', self.__class__.__name__)) - - def show_state(self): - """Display the agent's internal state. Subclasses should override.""" - print("I don't know how to show_state.") - - def display(self, canvas, x, y, width, height): - """Display an image of this Thing on the canvas.""" - # Do we need this? - pass - - -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: - percept: Define the percept that an agent sees. - execute_action: Define the effects of executing an action. - Also update the agent.performance slot. - The environment keeps a list of .things and .agents (which is a subset - of .things). Each agent has a .performance slot, initialized to 0. - Each thing has a .location slot, even though some environments may not - need this.""" - - def __init__(self, width=5, height=5): - 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 [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.""" - 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.)""" - raise NotImplementedError - - def default_location(self, thing): - """Default location to place a new thing with unspecified location.""" - 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 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.""" - 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): - self.step() - - def list_things_at(self, location, tclass=Thing): - """Return all things exactly at a given location.""" - return [thing for thing in self.things - if all(x == y for x, y in zip(thing.location, location)) and isinstance(thing, tclass)] - - def some_things_at(self, location, tclass=Thing): - """Return true if at least one of the things at location - is an instance of class tclass (or a subclass).""" - return self.list_things_at(location, tclass) != [] - - def add_thing(self, thing, location=None): - """Add a thing to the environment, setting its location. For - convenience, if thing is an agent program we make a new agent - for it. (Shouldn't need to override this.)""" - if not isinstance(thing, Thing): - thing = Agent(thing) - if thing in self.things: - print("Can't add the same thing twice") - else: - thing.location = location if location is not None else self.default_location(thing) - self.things.append(thing) - if isinstance(thing, Agent): - thing.performance = 0 - self.agents.append(thing) - - def delete_thing(self, thing): - """Remove a thing from the environment.""" - if thing in self.agents: - self.agents.remove(thing) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b14d285 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +PyQt5~=5.15.4 \ No newline at end of file diff --git a/setup.py b/setup.py index a37c2cd..ba947a2 100644 --- a/setup.py +++ b/setup.py @@ -6,8 +6,6 @@ author='Killian Mahé', author_email='killian.mahe1@uqac.ca', packages=[ - 'pretty-tables', - 'colorama', - 'numpy', + 'PyQt5', ] ) diff --git a/hoover-agent/__init__.py b/vacuum-agent/__init__.py similarity index 100% rename from hoover-agent/__init__.py rename to vacuum-agent/__init__.py diff --git a/vacuum-agent/__main__.py b/vacuum-agent/__main__.py new file mode 100644 index 0000000..62d417d --- /dev/null +++ b/vacuum-agent/__main__.py @@ -0,0 +1,116 @@ +from threading import Thread +import sys + +from agent import Environment, Dirt, Jewel, Position + +from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsScene, QGraphicsView +from PyQt5.QtCore import QThread, QRectF, Qt, QPointF +from PyQt5.QtGui import QBrush, QPolygonF + + +class EnvironmentThread(QThread): + + def __init__(self, environment: Environment): + QThread.__init__(self) + self.environment = environment + + def run(self): + self.environment.run() + + +class AgentThread(QThread): + + def run(self): + pass + + +def convert_position(position: Position): + return Position(100*position.x + 50, 100*position.y + 50) + + +class Window(QMainWindow): + + def __init__(self, parent=None): + super().__init__(parent) + self.central_widget = self.centralWidget() + self.scene = QGraphicsScene() + self.view = QGraphicsView(self.scene) + + self.environment = Environment() + self.environment.thing_spawn.connect(self.spawn_handler) + self.environment.thing_deleted.connect(self.deleted_thing_handler) + + self.env_thread = EnvironmentThread(self.environment) + self.env_thread.finished.connect(app.exit) + self.env_thread.start() + + self.dirt_rects = [] + self.jewel_rects = [] + + self.setup_ui() + self.view.show() + + def spawn_handler(self, thing): + if isinstance(thing, Dirt): + self.draw_dirt(thing.position) + elif isinstance(thing, Jewel): + self.draw_jewel(thing.position) + + def deleted_thing_handler(self, thing): + position = convert_position(thing.position) + if isinstance(thing, Dirt): + for i in range(0, len(self.dirt_rects)): + if self.dirt_rects[i].contains(QPointF(position.x, position.y)): + self.scene.removeItem(self.dirt_rects.pop(i)) + return + elif isinstance(thing, Jewel): + for i in range(0, len(self.jewel_rects)): + if self.jewel_rects[i].contains(QPointF(position.x, position.y)): + self.scene.removeItem(self.jewel_rects.pop(i)) + return + + def draw_jewel(self, position: Position): + position = convert_position(position) + brush = QBrush() + brush.setStyle(Qt.SolidPattern) + brush.setColor(Qt.cyan) + + self.jewel_rects.append(self.scene.addPolygon(QPolygonF([ + QPointF(position.x - 10, position.y), + QPointF(position.x, position.y - 10), + QPointF(position.x + 10, position.y), + QPointF(position.x, position.y + 20) + ]), brush=brush)) + + def draw_dirt(self, position: Position): + position = convert_position(position) + brush = QBrush() + brush.setStyle(Qt.Dense5Pattern) + brush.setColor(Qt.gray) + self.dirt_rects.append(self.scene.addRect(QRectF(position.x - 50, position.y - 50, 100, 100), + brush=brush)) + + def setup_ui(self): + self.setWindowTitle("Vacuum Agent") + self.resize(1000, 800) + self.setCentralWidget(self.view) + + # Build scene + self.scene.addRect(QRectF(0, 0, 500, 500)) + + for y in range(0, self.environment.y_max): + for x in range(0, self.environment.x_max): + self.scene.addRect(QRectF(100 * x, 100 * y, 100, 100)) + + +app = QApplication(sys.argv) +win = Window() +win.show() +sys.exit(app.exec()) + +# environment = Environment() +# agent = Agent() +# environment.agent = agent +# +# env_thread = Thread(environment.run()) +# env_thread.run() diff --git a/vacuum-agent/agent.py b/vacuum-agent/agent.py new file mode 100644 index 0000000..7ef33c9 --- /dev/null +++ b/vacuum-agent/agent.py @@ -0,0 +1,136 @@ +from random import random, randint +from time import sleep + +from PyQt5.QtCore import QObject, pyqtSignal + +from logic import State + + +class Position: + + def __init__(self, x=0, y=0): + self.x = x + self.y = y + + def __eq__(self, other): + if not isinstance(other, Position): + raise NotImplementedError + if self.x == other.x and self.y == other.y: + return True + return False + + def __str__(self): + return "(%s,%s)" % (self.x, self.y) + + +class Thing: + + def __init__(self, position: Position = None, x=0, y=0): + if position and isinstance(position, Position): + self.position = position + else: + self.position = Position(x, y) + + +class Dirt(Thing): + pass + + +class Jewel(Thing): + pass + + +class Environment(State, QObject): + + thing_spawn = pyqtSignal('PyQt_PyObject') + thing_deleted = pyqtSignal('PyQt_PyObject') + + def __init__(self): + QObject.__init__(self) + self.things = [] + self.agent = None + self.x_max = 5 + self.y_max = 5 + self.dirt_probability = 0.01 + self.jewel_probability = 0.001 + + def __eq__(self, other): + if isinstance(other, Environment): + return self.__hash__() == other.__hash__() + raise NotImplementedError + + def __hash__(self): + return hash({ + "things": self.things, + "agent": self.agent + }) + + def __str__(self): + pass + + def run(self): + while True: + if random() <= self.dirt_probability: + self.thing_spawn.emit(self.generate_dirt()) + if random() <= self.jewel_probability: + self.thing_spawn.emit(self.generate_jewel()) + sleep(0.005) + + def something_at(self, location, thing_class=None): + if issubclass(thing_class, Agent) and self.agent.position == location: + return self.agent + if issubclass(thing_class, Thing): + for thing in self.things: + if thing.position == location and isinstance(thing, thing_class): + return thing + return False + else: + raise NotImplementedError + + def random_location(self): + x = randint(0, self.x_max - 1) + y = randint(0, self.y_max - 1) + return Position(x, y) + + def generate_dirt(self): + position = self.random_location() + while self.something_at(position, Dirt): + position = self.random_location() + return self.add_thing(Dirt(position)) + + def add_thing(self, thing): + if isinstance(thing, Thing): + self.things.append(thing) + return thing + raise NotImplementedError + + def delete_thing(self, index): + if index < len(self.things): + self.thing_deleted.emit(self.things.pop(0)) + + def generate_jewel(self): + position = self.random_location() + while self.something_at(position, Jewel): + position = self.random_location() + return self.add_thing(Jewel(position)) + + +class Agent: + + def __init__(self, position=Position(0, 0)): + self.position = position + + def run(self): + pass + + def observe_environment_with_all_my_sensors(self): + pass + + def update_my_state(self): + pass + + def choose_an_action(self): + pass + + def just_do_it(self): + pass diff --git a/hoover-agent/logic.py b/vacuum-agent/logic.py similarity index 100% rename from hoover-agent/logic.py rename to vacuum-agent/logic.py