From 5eedbffa812cf21ff0a4c35b9708474814741e21 Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Tue, 6 Mar 2018 01:33:53 +0100 Subject: [PATCH 01/13] NF: Testing out a UI based on PyQt5 --- pelita/libpelita.py | 7 +- pelita/scripts/pelita_main.py | 9 ++- pelita/scripts/pelita_qtviewer.py | 53 +++++++++++++ pelita/scripts/pelita_tkviewer.py | 2 +- pelita/ui/qt/qt_viewer.py | 127 ++++++++++++++++++++++++++++++ setup.py | 1 + 6 files changed, 192 insertions(+), 7 deletions(-) create mode 100755 pelita/scripts/pelita_qtviewer.py create mode 100644 pelita/ui/qt/qt_viewer.py diff --git a/pelita/libpelita.py b/pelita/libpelita.py index 92974d1e5..4d01870e1 100644 --- a/pelita/libpelita.py +++ b/pelita/libpelita.py @@ -422,7 +422,9 @@ def channel_setup(publish_to=None, reply_to=None): yield { "publisher": publisher, "controller": controller } -def run_external_viewer(subscribe_sock, controller, geometry, delay, stop_after): +def run_external_viewer(subscribe_sock, controller, geometry, delay, stop_after, viewer=None): + if viewer is None: + viewer = 'pelita.scripts.pelita_tkviewer' # Something on OS X prevents Tk from running in a forked process. # Therefore we cannot use multiprocessing here. subprocess works, though. viewer_args = [ str(subscribe_sock) ] @@ -435,10 +437,9 @@ def run_external_viewer(subscribe_sock, controller, geometry, delay, stop_after) if stop_after is not None: viewer_args += ["--stop-after", str(stop_after)] - tkviewer = 'pelita.scripts.pelita_tkviewer' external_call = [get_python_process(), '-m', - tkviewer] + viewer_args + viewer] + viewer_args _logger.debug("Executing: %r", external_call) # os.setsid will keep the viewer from closing when the main process exits # a better solution might be to decouple the viewer from the main process diff --git a/pelita/scripts/pelita_main.py b/pelita/scripts/pelita_main.py index f0315cb34..b4229a8c9 100755 --- a/pelita/scripts/pelita_main.py +++ b/pelita/scripts/pelita_main.py @@ -183,6 +183,8 @@ def geometry_string(s): dest='viewer', help='Use the tk viewer (default).') viewer_opt.add_argument('--tk-no-sync', action='store_const', const='tk-no-sync', dest='viewer', help=argparse.SUPPRESS) +viewer_opt.add_argument('--qt', action='store_const', const='qt', + dest='viewer', help='use the qt viewer (default)') parser.set_defaults(viewer='tk') advanced_settings = parser.add_argument_group('Advanced settings') @@ -266,7 +268,7 @@ def main(): pass random.seed(args.seed) - if args.viewer.startswith('tk') and not args.publish_to: + if (args.viewer.startswith('tk') or args.viewer.startswith('qt')) and not args.publish_to: raise ValueError("Options --tk (or --tk-no-sync) and --no-publish are mutually exclusive.") try: @@ -352,14 +354,15 @@ def main(): viewers.append(ResultPrinter()) with libpelita.channel_setup(publish_to=args.publish_to) as channels: - if args.viewer.startswith('tk'): + if args.viewer.startswith('tk') or args.viewer.startswith('qt'): geometry = args.geometry delay = int(1000./args.fps) controller = channels["controller"] publisher = channels["publisher"] game_config["publisher"] = publisher + viewer_type = 'pelita.scripts.pelita_qtviewer' if args.viewer.startswith('qt') else None viewer = libpelita.run_external_viewer(publisher.socket_addr, controller.socket_addr, - geometry=geometry, delay=delay, stop_after=args.stop_after) + geometry=geometry, delay=delay, stop_after=args.stop_after, viewer=viewer_type) libpelita.run_game(team_specs=team_specs, game_config=game_config, viewers=viewers, controller=controller) else: libpelita.run_game(team_specs=team_specs, game_config=game_config, viewers=viewers) diff --git a/pelita/scripts/pelita_qtviewer.py b/pelita/scripts/pelita_qtviewer.py new file mode 100755 index 000000000..d81f54817 --- /dev/null +++ b/pelita/scripts/pelita_qtviewer.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +import argparse +import sys + +from PyQt5.QtWidgets import QApplication + + +from pelita.ui.qt.qt_viewer import QtViewer + + + +def geometry_string(s): + """Get a X-style geometry definition and return a tuple. + + 600x400 -> (600,400) + """ + try: + x_string, y_string = s.split('x') + geometry = (int(x_string), int(y_string)) + except ValueError: + msg = "%s is not a valid geometry specification" %s + raise argparse.ArgumentTypeError(msg) + return geometry + +parser = argparse.ArgumentParser(description='Open a Qt viewer') +parser.add_argument('subscribe_sock', metavar="URL", type=str, + help='subscribe socket') +parser.add_argument('--controller-address', metavar="URL", type=str, + help='controller address') +parser.add_argument('--geometry', type=geometry_string, + help='geometry') +parser.add_argument('--delay', type=int, + help='delay') + +def main(): + args = parser.parse_args() + viewer_args = { + 'address': args.subscribe_sock, + 'controller_address': args.controller_address, + 'geometry': args.geometry, + 'delay': args.delay + } + + app = QApplication(sys.argv) + print(viewer_args) + mainWindow = QtViewer(**{k: v for k, v in list(viewer_args.items()) if v is not None}) + mainWindow.show() + sys.exit(app.exec_()) + +if __name__ == '__main__': + main() + diff --git a/pelita/scripts/pelita_tkviewer.py b/pelita/scripts/pelita_tkviewer.py index d8fc7645a..9d828616f 100755 --- a/pelita/scripts/pelita_tkviewer.py +++ b/pelita/scripts/pelita_tkviewer.py @@ -59,7 +59,7 @@ def main(): 'delay': args.delay, 'stop_after': args.stop_after } - v = TkViewer(**{k: v for k, v in list(tkargs.items()) if v is not None}) + v = TkViewer(**{k: v for k, v in tkargs.items() if v is not None}) v.run() if __name__ == '__main__': diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py new file mode 100644 index 000000000..677dd4e6d --- /dev/null +++ b/pelita/ui/qt/qt_viewer.py @@ -0,0 +1,127 @@ +import json +import logging +import os +import shutil +import signal +import sys + +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtWidgets import QWidget, QApplication, QMainWindow +import zmq + +_logger = logging.getLogger(__name__) +_logger.setLevel(logging.DEBUG) + +signal.signal(signal.SIGINT, signal.SIG_DFL) + +class ZMQListener(QtCore.QThread): + message = QtCore.pyqtSignal(str) + + def __init__(self, address): + super().__init__() + self.address = address + self.running = True + + def run(self): + self.context = zmq.Context() + self.socket = self.context.socket(zmq.SUB) + self.socket.setsockopt_unicode(zmq.SUBSCRIBE, "") + self.socket.connect(self.address) + self.poll = zmq.Poller() + self.poll.register(self.socket, zmq.POLLIN) + + while self.running: + evts = dict(self.poll.poll(1000)) + if self.socket in evts: + message = self.socket.recv_unicode() + self.message.emit(message) + + @QtCore.pyqtSlot() + def exit_thread(self): + self.running = False + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.resize(500, 500) + self.centralwidget = QWidget(MainWindow) + self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 500, 22)) + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + MainWindow.setStatusBar(self.statusbar) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + +class QtViewer(QMainWindow): + exit_thread = QtCore.pyqtSignal() + + def __init__(self, address, controller_address=None, geometry=None, delay=None, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.zmq_listener = ZMQListener(address) + self.exit_thread.connect(self.zmq_listener.exit_thread) + + self.zmq_listener.message.connect(self.signal_received) + + QtCore.QTimer.singleShot(0, self.zmq_listener.start) + + self.context = zmq.Context() + if controller_address: + self.controller_socket = self.context.socket(zmq.DEALER) + self.controller_socket.connect(controller_address) + else: + self.controller_socket = None + + if self.controller_socket: + QtCore.QTimer.singleShot(0, self.request_initial) + + self.setupUi() + + self.ui = Ui_MainWindow() # This is from a python export from QtDesigner + self.ui.setupUi(self) + + def paintEvent(self, event): + painter = QtGui.QPainter(self) + painter.setPen(QtGui.QPen(QtCore.Qt.red)) + painter.drawArc(QtCore.QRectF(250, 250, 10, 10), 0, 5760) + + def setupUi(self): + self.setObjectName("MainWindow") + self.resize(277, 244) + self.statusbar = QtWidgets.QStatusBar() + self.statusbar.setObjectName("statusbar") + self.setStatusBar(self.statusbar) + + def request_initial(self): + if self.controller_socket: + self.controller_socket.send_json({"__action__": "set_initial"}) + + def request_step(self): + if self.controller_socket: + self.controller_socket.send_json({"__action__": "play_step"}) + + def signal_received(self, message): + message = json.loads(message) + observed = message["__data__"] + if observed: + self.observe(observed) + QtCore.QTimer.singleShot(0, self.request_step) + + def observe(self, observed): + from pelita.datamodel import CTFUniverse + universe = observed.get("universe") + universe = CTFUniverse._from_json_dict(universe) if universe else None + game_state = observed.get("game_state") + + if universe: + self.statusBar().showMessage(str([b.current_pos for b in universe.bots])) + + def closeEvent(self, event): + self.exit_thread.emit() + self.zmq_listener.wait(2000) + if self.controller_socket: + self.controller_socket.send_json({"__action__": "exit"}) + event.accept() + diff --git a/setup.py b/setup.py index 0d3429b5e..fb63219b6 100644 --- a/setup.py +++ b/setup.py @@ -146,6 +146,7 @@ def run_tests(self): 'pelita=pelita.scripts.pelita_main:main', 'pelita-tournament=pelita.scripts.pelita_tournament:main', 'pelita-tkviewer=pelita.scripts.pelita_tkviewer:main', + 'pelita-qtviewer=pelita.scripts.pelita_qtviewer:main', 'pelita-player=pelita.scripts.pelita_player:main', ], }, From cc4427d830fecc54a846eb901bfc2ba6cd854f03 Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Tue, 6 Mar 2018 02:18:35 +0100 Subject: [PATCH 02/13] ENH: Food and pause button. --- pelita/ui/qt/qt_viewer.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py index 677dd4e6d..4e5e8396a 100644 --- a/pelita/ui/qt/qt_viewer.py +++ b/pelita/ui/qt/qt_viewer.py @@ -82,10 +82,33 @@ def __init__(self, address, controller_address=None, geometry=None, delay=None, self.ui = Ui_MainWindow() # This is from a python export from QtDesigner self.ui.setupUi(self) + self.running = True + + pause = QtWidgets.QShortcut(" ", self) + pause.activated.connect(self.pause) + + self.positions = [] + self.food = [] + + @QtCore.pyqtSlot() + def pause(self): + self.running = not self.running + self.request_next() + def paintEvent(self, event): painter = QtGui.QPainter(self) - painter.setPen(QtGui.QPen(QtCore.Qt.red)) - painter.drawArc(QtCore.QRectF(250, 250, 10, 10), 0, 5760) + for food in self.food: + painter.setPen(QtGui.QPen(QtCore.Qt.black)) + painter.drawEllipse(QtCore.QRectF(food[0] * 10, food[1] * 10, 3, 3)) + if self.positions: + def paint(pos): + painter.drawArc(QtCore.QRectF(pos[0] * 10, pos[1] * 10, 10, 10), 0, 5760) + painter.setPen(QtGui.QPen(QtCore.Qt.blue)) + paint(self.positions[0]) + paint(self.positions[2]) + painter.setPen(QtGui.QPen(QtCore.Qt.red)) + paint(self.positions[1]) + paint(self.positions[3]) def setupUi(self): self.setObjectName("MainWindow") @@ -98,6 +121,10 @@ def request_initial(self): if self.controller_socket: self.controller_socket.send_json({"__action__": "set_initial"}) + def request_next(self): + if self.running: + self.request_step() + def request_step(self): if self.controller_socket: self.controller_socket.send_json({"__action__": "play_step"}) @@ -107,7 +134,6 @@ def signal_received(self, message): observed = message["__data__"] if observed: self.observe(observed) - QtCore.QTimer.singleShot(0, self.request_step) def observe(self, observed): from pelita.datamodel import CTFUniverse @@ -116,7 +142,12 @@ def observe(self, observed): game_state = observed.get("game_state") if universe: + self.positions = [b.current_pos for b in universe.bots] + self.food = universe.food self.statusBar().showMessage(str([b.current_pos for b in universe.bots])) + self.repaint() + if self.running: + QtCore.QTimer.singleShot(0, self.request_next) def closeEvent(self, event): self.exit_thread.emit() From 0601f7f482742c4dd0fe0a0f9686ff0b2b3f3674 Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Tue, 6 Mar 2018 13:34:15 +0100 Subject: [PATCH 03/13] ENH: Walls and scaling. --- pelita/ui/qt/qt_viewer.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py index 4e5e8396a..46f8286f2 100644 --- a/pelita/ui/qt/qt_viewer.py +++ b/pelita/ui/qt/qt_viewer.py @@ -87,6 +87,7 @@ def __init__(self, address, controller_address=None, geometry=None, delay=None, pause = QtWidgets.QShortcut(" ", self) pause.activated.connect(self.pause) + self.universe = None self.positions = [] self.food = [] @@ -97,16 +98,28 @@ def pause(self): def paintEvent(self, event): painter = QtGui.QPainter(self) + width = self.width() + height = self.height() + pen_size = 0.1 + if self.universe: + universe_width = self.universe.maze.width + universe_height = self.universe.maze.height + painter.scale(width / universe_width, height / universe_height) + painter.setPen(QtGui.QPen(QtCore.Qt.black, pen_size)) + for position, wall in self.universe.maze.items(): + if wall: + painter.drawArc(QtCore.QRectF(position[0] + 0.1, position[1] + 0.1, 0.8, 0.8), 0, 5760) + for food in self.food: - painter.setPen(QtGui.QPen(QtCore.Qt.black)) - painter.drawEllipse(QtCore.QRectF(food[0] * 10, food[1] * 10, 3, 3)) + painter.setPen(QtGui.QPen(QtCore.Qt.black, pen_size)) + painter.drawEllipse(QtCore.QRectF(food[0] + 0.3, food[1] + 0.3, 0.4, 0.4)) if self.positions: def paint(pos): - painter.drawArc(QtCore.QRectF(pos[0] * 10, pos[1] * 10, 10, 10), 0, 5760) - painter.setPen(QtGui.QPen(QtCore.Qt.blue)) + painter.drawArc(QtCore.QRectF(pos[0] + 0.2, pos[1] + 0.2, 0.6, 0.6), 0, 5760) + painter.setPen(QtGui.QPen(QtCore.Qt.blue, pen_size)) paint(self.positions[0]) paint(self.positions[2]) - painter.setPen(QtGui.QPen(QtCore.Qt.red)) + painter.setPen(QtGui.QPen(QtCore.Qt.red, pen_size)) paint(self.positions[1]) paint(self.positions[3]) @@ -144,6 +157,7 @@ def observe(self, observed): if universe: self.positions = [b.current_pos for b in universe.bots] self.food = universe.food + self.universe = universe self.statusBar().showMessage(str([b.current_pos for b in universe.bots])) self.repaint() if self.running: From 83fcc6088e5e08d21c50432996370b21ef2b3ffe Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Tue, 6 Mar 2018 13:35:49 +0100 Subject: [PATCH 04/13] ENH: Add Antialiasing. --- pelita/ui/qt/qt_viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py index 46f8286f2..66f8a9848 100644 --- a/pelita/ui/qt/qt_viewer.py +++ b/pelita/ui/qt/qt_viewer.py @@ -98,6 +98,7 @@ def pause(self): def paintEvent(self, event): painter = QtGui.QPainter(self) + painter.setRenderHint(QtGui.QPainter.Antialiasing) width = self.width() height = self.height() pen_size = 0.1 From 9f6a9d2599d76db86e380a09f5dd160956c6a709 Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Tue, 6 Mar 2018 17:06:10 +0100 Subject: [PATCH 05/13] =?UTF-8?q?RF:=20Use=20zmq=20signalling=20instead=20?= =?UTF-8?q?of=20Qt=20to=20take=20advantage=20of=20zmq=E2=80=99s=20polling.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pelita/ui/qt/qt_viewer.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py index 66f8a9848..90ef211f5 100644 --- a/pelita/ui/qt/qt_viewer.py +++ b/pelita/ui/qt/qt_viewer.py @@ -16,10 +16,11 @@ class ZMQListener(QtCore.QThread): message = QtCore.pyqtSignal(str) - - def __init__(self, address): + + def __init__(self, address, exit_address): super().__init__() - self.address = address + self.address = address + self.exit_address = exit_address self.running = True def run(self): @@ -27,18 +28,21 @@ def run(self): self.socket = self.context.socket(zmq.SUB) self.socket.setsockopt_unicode(zmq.SUBSCRIBE, "") self.socket.connect(self.address) + + self.exit_socket = self.context.socket(zmq.PAIR) + self.exit_socket.connect(self.exit_address) + self.poll = zmq.Poller() self.poll.register(self.socket, zmq.POLLIN) + self.poll.register(self.exit_socket, zmq.POLLIN) while self.running: evts = dict(self.poll.poll(1000)) if self.socket in evts: message = self.socket.recv_unicode() self.message.emit(message) - - @QtCore.pyqtSlot() - def exit_thread(self): - self.running = False + if self.exit_socket in evts: + self.running = False class Ui_MainWindow(object): @@ -54,20 +58,20 @@ def setupUi(self, MainWindow): MainWindow.setStatusBar(self.statusbar) QtCore.QMetaObject.connectSlotsByName(MainWindow) -class QtViewer(QMainWindow): - exit_thread = QtCore.pyqtSignal() +class QtViewer(QMainWindow): def __init__(self, address, controller_address=None, geometry=None, delay=None, *args, **kwargs): super().__init__(*args, **kwargs) - self.zmq_listener = ZMQListener(address) - self.exit_thread.connect(self.zmq_listener.exit_thread) + self.context = zmq.Context() + self.exit_socket = self.context.socket(zmq.PAIR) + exit_address = self.exit_socket.bind_to_random_port('tcp://127.0.0.1') + self.zmq_listener = ZMQListener(address, f'tcp://127.0.0.1:{exit_address}') self.zmq_listener.message.connect(self.signal_received) QtCore.QTimer.singleShot(0, self.zmq_listener.start) - self.context = zmq.Context() if controller_address: self.controller_socket = self.context.socket(zmq.DEALER) self.controller_socket.connect(controller_address) @@ -165,7 +169,7 @@ def observe(self, observed): QtCore.QTimer.singleShot(0, self.request_next) def closeEvent(self, event): - self.exit_thread.emit() + self.exit_socket.send(b'') self.zmq_listener.wait(2000) if self.controller_socket: self.controller_socket.send_json({"__action__": "exit"}) From fce4e4ca5531015492d3bf37e4ae3e7e1e91fe65 Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Tue, 6 Mar 2018 18:51:26 +0100 Subject: [PATCH 06/13] ENH: Rotation --- pelita/ui/qt/qt_viewer.py | 115 +++++++++++++++++++++++++++++++------- 1 file changed, 96 insertions(+), 19 deletions(-) diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py index 90ef211f5..6157c8fa2 100644 --- a/pelita/ui/qt/qt_viewer.py +++ b/pelita/ui/qt/qt_viewer.py @@ -1,13 +1,20 @@ +import cmath import json import logging +import math import os import shutil import signal import sys +import zmq + from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtWidgets import QWidget, QApplication, QMainWindow -import zmq +from PyQt5.QtCore import QPointF, QRectF + +from pelita.graph import diff_pos +from pelita.datamodel import CTFUniverse _logger = logging.getLogger(__name__) _logger.setLevel(logging.DEBUG) @@ -92,8 +99,10 @@ def __init__(self, address, controller_address=None, geometry=None, delay=None, pause.activated.connect(self.pause) self.universe = None - self.positions = [] self.food = [] + self.previous_positions = {} + self.directions = {} + @QtCore.pyqtSlot() def pause(self): @@ -105,28 +114,87 @@ def paintEvent(self, event): painter.setRenderHint(QtGui.QPainter.Antialiasing) width = self.width() height = self.height() - pen_size = 0.1 + pen_size = 0.05 + if self.universe: universe_width = self.universe.maze.width universe_height = self.universe.maze.height painter.scale(width / universe_width, height / universe_height) painter.setPen(QtGui.QPen(QtCore.Qt.black, pen_size)) + + for food in self.food: + painter.setPen(QtGui.QPen(QtCore.Qt.black, pen_size)) + painter.setBrush(QtGui.QColor(247, 150, 213)) + painter.drawEllipse(QRectF(food[0] + 0.3, food[1] + 0.3, 0.4, 0.4)) + for position, wall in self.universe.maze.items(): if wall: - painter.drawArc(QtCore.QRectF(position[0] + 0.1, position[1] + 0.1, 0.8, 0.8), 0, 5760) + painter.setBrush(QtGui.QBrush()) + painter.drawEllipse(QRectF(position[0] + 0.1, position[1] + 0.1, 0.8, 0.8)) - for food in self.food: - painter.setPen(QtGui.QPen(QtCore.Qt.black, pen_size)) - painter.drawEllipse(QtCore.QRectF(food[0] + 0.3, food[1] + 0.3, 0.4, 0.4)) - if self.positions: - def paint(pos): - painter.drawArc(QtCore.QRectF(pos[0] + 0.2, pos[1] + 0.2, 0.6, 0.6), 0, 5760) - painter.setPen(QtGui.QPen(QtCore.Qt.blue, pen_size)) - paint(self.positions[0]) - paint(self.positions[2]) - painter.setPen(QtGui.QPen(QtCore.Qt.red, pen_size)) - paint(self.positions[1]) - paint(self.positions[3]) + blue_col = QtGui.QColor(94, 158, 217) + red_col = QtGui.QColor(235, 90, 90) + + for bot in self.universe.bots: + def paint(pos): + painter.drawArc(QRectF(pos[0] + 0.2, pos[1] + 0.2, 0.6, 0.6), 0, 5760) + + def paint_harvester(pos, color): + direction = self.directions.get(bot.index) + if not direction: + direction = (0, 1) + + rotation = math.degrees(cmath.phase(direction[0] - direction[1]*1j)) + + x, y = pos + bounding_rect = QRectF(x, y, 1, 1) + # bot body + path = QtGui.QPainterPath(QPointF(x + 0.5, y + 0.5)) + path.arcTo(bounding_rect, 20 + rotation, 320) + path.closeSubpath() + painter.setBrush(color) + + painter.drawPath(path) + + painter.setBrush(QtGui.QColor(235, 235, 30)) + eye_size = 0.1 + # left eye + painter.drawEllipse(QRectF(x + 0.3 - eye_size, y + 0.3 - eye_size, eye_size * 2, eye_size * 2)) + + def paint_destroyer(pos, color): + x, y = pos + bounding_rect = QRectF(x, y, 1, 1) + # ghost head + path = QtGui.QPainterPath(QPointF(x, y + 0.5)) + path.lineTo(QPointF(x, y + 7/8)) + path.cubicTo(QPointF(x + 0.5/6, y + 7/8), QPointF(x + 1/6, y + 7/8), QPointF(x + 1/6, y + 1/2)) + path.cubicTo(QPointF(x + 1/6, y + 1), QPointF(x + 3/6, y + 1), QPointF(x + 3/6, y + 1/2)) + path.cubicTo(QPointF(x + 3/6, y + 1), QPointF(x + 5/6, y + 1), QPointF(x + 5/6, y + 1/2)) + path.cubicTo(QPointF(x + 5/6, y + 7/8), QPointF(x + 5.5/6, y + 7/8), QPointF(x + 6/6, y + 7/8)) + path.lineTo(QPointF(x + 1, y + 0.5)) + path.cubicTo(QPointF(x + 1, y), QPointF(x, y), QPointF(x, y+0.5)) + painter.setBrush(color) + + painter.drawPath(path) + + # ghost eyes + eye_size = 0.15 + painter.setBrush(QtGui.QColor(235, 235, 30)) + # left eye + painter.drawEllipse(QRectF(x + 0.3 - eye_size, y + 0.3 - eye_size, eye_size * 2, eye_size * 2)) + # right eye + painter.drawEllipse(QRectF(x + 0.7 - eye_size, y + 0.3 - eye_size, eye_size * 2, eye_size * 2)) + + if bot.team_index == 0: + bot_col = blue_col + else: + bot_col = red_col + + painter.setPen(QtGui.QPen(bot_col, pen_size)) + if bot.is_destroyer: + paint_destroyer(bot.current_pos, bot_col) + else: + paint_harvester(bot.current_pos, bot_col) def setupUi(self): self.setObjectName("MainWindow") @@ -154,24 +222,33 @@ def signal_received(self, message): self.observe(observed) def observe(self, observed): - from pelita.datamodel import CTFUniverse universe = observed.get("universe") universe = CTFUniverse._from_json_dict(universe) if universe else None game_state = observed.get("game_state") if universe: - self.positions = [b.current_pos for b in universe.bots] self.food = universe.food self.universe = universe self.statusBar().showMessage(str([b.current_pos for b in universe.bots])) + + for bot in universe.bots: + previous_pos = self.previous_positions.get(bot.index) + if previous_pos: + diff = diff_pos(previous_pos, bot.current_pos) + if abs(diff[0]) + abs(diff[1]) == 1: + self.directions[bot.index] = diff + + self.previous_positions[bot.index] = bot.current_pos + self.repaint() + if self.running: QtCore.QTimer.singleShot(0, self.request_next) def closeEvent(self, event): self.exit_socket.send(b'') - self.zmq_listener.wait(2000) if self.controller_socket: self.controller_socket.send_json({"__action__": "exit"}) + #self.zmq_listener.wait(2000) event.accept() From 2060691d41b03544b66886e50bc824ea987c0dad Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Tue, 6 Mar 2018 22:43:54 +0100 Subject: [PATCH 07/13] ENH: Nicer UI (demo) --- pelita/ui/qt/qt_viewer.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py index 6157c8fa2..ec2b8ea81 100644 --- a/pelita/ui/qt/qt_viewer.py +++ b/pelita/ui/qt/qt_viewer.py @@ -54,15 +54,15 @@ def run(self): class Ui_MainWindow(object): def setupUi(self, MainWindow): - MainWindow.resize(500, 500) + MainWindow.resize(600, 300) self.centralwidget = QWidget(MainWindow) self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 500, 22)) MainWindow.setMenuBar(self.menubar) - self.statusbar = QtWidgets.QStatusBar(MainWindow) - MainWindow.setStatusBar(self.statusbar) +# self.statusbar = QtWidgets.QStatusBar(MainWindow) +# MainWindow.setStatusBar(self.statusbar) QtCore.QMetaObject.connectSlotsByName(MainWindow) @@ -116,6 +116,9 @@ def paintEvent(self, event): height = self.height() pen_size = 0.05 + blue_col = QtGui.QColor(94, 158, 217) + red_col = QtGui.QColor(235, 90, 90) + if self.universe: universe_width = self.universe.maze.width universe_height = self.universe.maze.height @@ -129,11 +132,18 @@ def paintEvent(self, event): for position, wall in self.universe.maze.items(): if wall: - painter.setBrush(QtGui.QBrush()) + if position[0] < self.universe.maze.width / 2: + brush = QtGui.QBrush(blue_col, QtCore.Qt.Dense4Pattern) + inverted = painter.worldTransform().inverted() + brush.setTransform(inverted[0]) + painter.setBrush(brush) + else: + brush = QtGui.QBrush(red_col, QtCore.Qt.Dense4Pattern) + inverted = painter.worldTransform().inverted() + brush.setTransform(inverted[0]) + painter.setBrush(brush) painter.drawEllipse(QRectF(position[0] + 0.1, position[1] + 0.1, 0.8, 0.8)) - blue_col = QtGui.QColor(94, 158, 217) - red_col = QtGui.QColor(235, 90, 90) for bot in self.universe.bots: def paint(pos): @@ -197,11 +207,10 @@ def paint_destroyer(pos, color): paint_harvester(bot.current_pos, bot_col) def setupUi(self): - self.setObjectName("MainWindow") - self.resize(277, 244) - self.statusbar = QtWidgets.QStatusBar() - self.statusbar.setObjectName("statusbar") - self.setStatusBar(self.statusbar) + self.setObjectName("Pelita") +# self.statusbar = QtWidgets.QStatusBar() +# self.statusbar.setObjectName("statusbar") +# self.setStatusBar(self.statusbar) def request_initial(self): if self.controller_socket: @@ -229,7 +238,7 @@ def observe(self, observed): if universe: self.food = universe.food self.universe = universe - self.statusBar().showMessage(str([b.current_pos for b in universe.bots])) +# self.statusBar().showMessage(str([b.current_pos for b in universe.bots])) for bot in universe.bots: previous_pos = self.previous_positions.get(bot.index) From 4450ba7b49e6c5dcbcfc4b89bf742ed10f114804 Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Tue, 6 Mar 2018 23:01:28 +0100 Subject: [PATCH 08/13] NF: Qt can now export directly to png. --- pelita/libpelita.py | 4 +++- pelita/scripts/pelita_main.py | 3 ++- pelita/scripts/pelita_qtviewer.py | 4 +++- pelita/ui/qt/qt_viewer.py | 23 +++++++++++++++++++++-- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/pelita/libpelita.py b/pelita/libpelita.py index 4d01870e1..0f49b63dd 100644 --- a/pelita/libpelita.py +++ b/pelita/libpelita.py @@ -422,7 +422,7 @@ def channel_setup(publish_to=None, reply_to=None): yield { "publisher": publisher, "controller": controller } -def run_external_viewer(subscribe_sock, controller, geometry, delay, stop_after, viewer=None): +def run_external_viewer(subscribe_sock, controller, geometry, delay, stop_after, viewer=None, export=None): if viewer is None: viewer = 'pelita.scripts.pelita_tkviewer' # Something on OS X prevents Tk from running in a forked process. @@ -436,6 +436,8 @@ def run_external_viewer(subscribe_sock, controller, geometry, delay, stop_after, viewer_args += ["--delay", str(delay)] if stop_after is not None: viewer_args += ["--stop-after", str(stop_after)] + if export: + viewer_args += ["--export", str(export)] external_call = [get_python_process(), '-m', diff --git a/pelita/scripts/pelita_main.py b/pelita/scripts/pelita_main.py index b4229a8c9..880d43af0 100755 --- a/pelita/scripts/pelita_main.py +++ b/pelita/scripts/pelita_main.py @@ -133,6 +133,7 @@ def geometry_string(s): #' DUMPFILE (default \'pelita.dump\').', metavar='DUMPFILE', default=argparse.SUPPRESS, nargs='?', help=argparse.SUPPRESS) +parser.add_argument('--export', type=str, metavar="FOLDER", help=argparse.SUPPRESS) parser.add_argument('--dry-run', const=True, action='store_const', help=argparse.SUPPRESS) #help='Load players but do not actually play the game.') parser.add_argument('--list-layouts', action='store_const', const=True, @@ -362,7 +363,7 @@ def main(): game_config["publisher"] = publisher viewer_type = 'pelita.scripts.pelita_qtviewer' if args.viewer.startswith('qt') else None viewer = libpelita.run_external_viewer(publisher.socket_addr, controller.socket_addr, - geometry=geometry, delay=delay, stop_after=args.stop_after, viewer=viewer_type) + geometry=geometry, delay=delay, stop_after=args.stop_after, viewer=viewer_type, export=args.export) libpelita.run_game(team_specs=team_specs, game_config=game_config, viewers=viewers, controller=controller) else: libpelita.run_game(team_specs=team_specs, game_config=game_config, viewers=viewers) diff --git a/pelita/scripts/pelita_qtviewer.py b/pelita/scripts/pelita_qtviewer.py index d81f54817..77cec2eae 100755 --- a/pelita/scripts/pelita_qtviewer.py +++ b/pelita/scripts/pelita_qtviewer.py @@ -32,6 +32,7 @@ def geometry_string(s): help='geometry') parser.add_argument('--delay', type=int, help='delay') +parser.add_argument('--export', type=str, metavar="FOLDER", help='png export path') def main(): args = parser.parse_args() @@ -39,7 +40,8 @@ def main(): 'address': args.subscribe_sock, 'controller_address': args.controller_address, 'geometry': args.geometry, - 'delay': args.delay + 'delay': args.delay, + 'export': args.export } app = QApplication(sys.argv) diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py index ec2b8ea81..29eb2c911 100644 --- a/pelita/ui/qt/qt_viewer.py +++ b/pelita/ui/qt/qt_viewer.py @@ -3,6 +3,7 @@ import logging import math import os +from pathlib import Path import shutil import signal import sys @@ -67,9 +68,19 @@ def setupUi(self, MainWindow): class QtViewer(QMainWindow): - def __init__(self, address, controller_address=None, geometry=None, delay=None, *args, **kwargs): + def __init__(self, address, controller_address=None, + geometry=None, delay=None, export=None, + *args, **kwargs): super().__init__(*args, **kwargs) + if export: + png_export_path = Path(export) + if not png_export_path.is_dir(): + raise RuntimeError(f"Not a directory: {png_export_path}") + self.png_export_path = png_export_path + else: + self.png_export_path = None + self.context = zmq.Context() self.exit_socket = self.context.socket(zmq.PAIR) exit_address = self.exit_socket.bind_to_random_port('tcp://127.0.0.1') @@ -103,7 +114,6 @@ def __init__(self, address, controller_address=None, geometry=None, delay=None, self.previous_positions = {} self.directions = {} - @QtCore.pyqtSlot() def pause(self): self.running = not self.running @@ -250,6 +260,15 @@ def observe(self, observed): self.previous_positions[bot.index] = bot.current_pos self.repaint() + if self.png_export_path: + try: + round_index = game_state['round_index'] + bot_id = game_state['bot_id'] + file_name = f'pelita-{round_index}-{bot_id}.png' + + self.grab().save(str(self.png_export_path / file_name)) + except TypeError as e: + print(e) if self.running: QtCore.QTimer.singleShot(0, self.request_next) From 98d9905658f6e77b78529fcf3c3365f86e6741e6 Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Wed, 7 Mar 2018 23:35:33 +0100 Subject: [PATCH 09/13] ENH: More shortcuts. --- pelita/ui/qt/qt_viewer.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py index 29eb2c911..a58ed92ac 100644 --- a/pelita/ui/qt/qt_viewer.py +++ b/pelita/ui/qt/qt_viewer.py @@ -106,8 +106,10 @@ def __init__(self, address, controller_address=None, self.running = True - pause = QtWidgets.QShortcut(" ", self) - pause.activated.connect(self.pause) + QtWidgets.QShortcut(" ", self).activated.connect(self.pause) + QtWidgets.QShortcut("q", self).activated.connect(self.close) + QtWidgets.QShortcut(QtCore.Qt.Key_Return, self).activated.connect(self.request_step) + QtWidgets.QShortcut(QtCore.Qt.Key_Return + QtCore.Qt.SHIFT, self).activated.connect(self.request_round) self.universe = None self.food = [] @@ -234,6 +236,10 @@ def request_step(self): if self.controller_socket: self.controller_socket.send_json({"__action__": "play_step"}) + def request_round(self): + if self.controller_socket: + self.controller_socket.send_json({"__action__": "play_round"}) + def signal_received(self, message): message = json.loads(message) observed = message["__data__"] From 0e6064c5a23e762849f945dad37787210534b7c0 Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Thu, 8 Mar 2018 16:59:17 +0100 Subject: [PATCH 10/13] ENH: Better eye location. --- pelita/ui/qt/qt_viewer.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py index a58ed92ac..d89ab43c3 100644 --- a/pelita/ui/qt/qt_viewer.py +++ b/pelita/ui/qt/qt_viewer.py @@ -180,8 +180,14 @@ def paint_harvester(pos, color): painter.setBrush(QtGui.QColor(235, 235, 30)) eye_size = 0.1 - # left eye - painter.drawEllipse(QRectF(x + 0.3 - eye_size, y + 0.3 - eye_size, eye_size * 2, eye_size * 2)) + if direction == (0, 1): # down + painter.drawEllipse(QRectF(x + 0.3 - eye_size, y + 0.4 - eye_size, eye_size * 2, eye_size * 2)) + elif direction == (1, 0): # right + painter.drawEllipse(QRectF(x + 0.4 - eye_size, y + 0.3 - eye_size, eye_size * 2, eye_size * 2)) + elif direction == (0, -1): # up + painter.drawEllipse(QRectF(x + 0.3 - eye_size, y + 0.6 - eye_size, eye_size * 2, eye_size * 2)) + elif direction == (-1, 0): # left + painter.drawEllipse(QRectF(x + 0.6 - eye_size, y + 0.3 - eye_size, eye_size * 2, eye_size * 2)) def paint_destroyer(pos, color): x, y = pos From 665301891532b8998fff6f3cc0c9afdd987b11f4 Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Sun, 11 Mar 2018 00:35:57 +0100 Subject: [PATCH 11/13] ENH: Naive caching. --- pelita/ui/qt/qt_pixmaps.py | 38 ++++++++++++++++++++++++++++++++++++++ pelita/ui/qt/qt_viewer.py | 27 ++++++++++++--------------- 2 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 pelita/ui/qt/qt_pixmaps.py diff --git a/pelita/ui/qt/qt_pixmaps.py b/pelita/ui/qt/qt_pixmaps.py new file mode 100644 index 000000000..9146fc437 --- /dev/null +++ b/pelita/ui/qt/qt_pixmaps.py @@ -0,0 +1,38 @@ + +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtCore import QRectF + +def generate_wall(maze, QWidget): + pixmap = QtGui.QPixmap(QWidget.width() * 2, QWidget.height() * 2) + pixmap.fill(QtCore.Qt.transparent) + pixmap.setDevicePixelRatio(2.0) + + universe_width = QWidget.universe.maze.width + universe_height = QWidget.universe.maze.height + painter = QtGui.QPainter(pixmap) + painter.setRenderHint(QtGui.QPainter.Antialiasing) + width = QWidget.width() + height = QWidget.height() + + painter.scale(width / universe_width, height / universe_height) + pen_size = 0.05 + painter.setPen(QtGui.QPen(QtCore.Qt.black, pen_size)) + + blue_col = QtGui.QColor(94, 158, 217) + red_col = QtGui.QColor(235, 90, 90) + + for position, wall in maze.items(): + if wall: + if position[0] < QWidget.universe.maze.width / 2: + brush = QtGui.QBrush(blue_col, QtCore.Qt.Dense4Pattern) + inverted = painter.worldTransform().inverted() + brush.setTransform(inverted[0]) + painter.setBrush(brush) + else: + brush = QtGui.QBrush(red_col, QtCore.Qt.Dense4Pattern) + inverted = painter.worldTransform().inverted() + brush.setTransform(inverted[0]) + painter.setBrush(brush) + painter.drawEllipse(QRectF(position[0] + 0.1, position[1] + 0.1, 0.8, 0.8)) + + return pixmap diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py index d89ab43c3..c85089d23 100644 --- a/pelita/ui/qt/qt_viewer.py +++ b/pelita/ui/qt/qt_viewer.py @@ -121,6 +121,10 @@ def pause(self): self.running = not self.running self.request_next() + def resizeEvent(self, event): + if hasattr(self, 'wall_pm'): + del self.wall_pm + def paintEvent(self, event): painter = QtGui.QPainter(self) painter.setRenderHint(QtGui.QPainter.Antialiasing) @@ -132,6 +136,14 @@ def paintEvent(self, event): red_col = QtGui.QColor(235, 90, 90) if self.universe: + + try: + painter.drawPixmap(0, 0, self.width(), self.height(), self.wall_pm) + except Exception: + from .qt_pixmaps import generate_wall + self.wall_pm = generate_wall(self.universe.maze, self) + painter.drawPixmap(0, 0, self.width(), self.height(), self.wall_pm) + universe_width = self.universe.maze.width universe_height = self.universe.maze.height painter.scale(width / universe_width, height / universe_height) @@ -142,21 +154,6 @@ def paintEvent(self, event): painter.setBrush(QtGui.QColor(247, 150, 213)) painter.drawEllipse(QRectF(food[0] + 0.3, food[1] + 0.3, 0.4, 0.4)) - for position, wall in self.universe.maze.items(): - if wall: - if position[0] < self.universe.maze.width / 2: - brush = QtGui.QBrush(blue_col, QtCore.Qt.Dense4Pattern) - inverted = painter.worldTransform().inverted() - brush.setTransform(inverted[0]) - painter.setBrush(brush) - else: - brush = QtGui.QBrush(red_col, QtCore.Qt.Dense4Pattern) - inverted = painter.worldTransform().inverted() - brush.setTransform(inverted[0]) - painter.setBrush(brush) - painter.drawEllipse(QRectF(position[0] + 0.1, position[1] + 0.1, 0.8, 0.8)) - - for bot in self.universe.bots: def paint(pos): painter.drawArc(QRectF(pos[0] + 0.2, pos[1] + 0.2, 0.6, 0.6), 0, 5760) From 071722a5b3fc4a158f51387da5c8c4990839c9e9 Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Mon, 12 Mar 2018 16:35:24 +0100 Subject: [PATCH 12/13] WIP --- pelita/ui/qt/qt_pixmaps.py | 81 ++++++++++++++++++++++++++++++++++++-- pelita/ui/qt/qt_viewer.py | 18 ++++++--- 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/pelita/ui/qt/qt_pixmaps.py b/pelita/ui/qt/qt_pixmaps.py index 9146fc437..7d453dced 100644 --- a/pelita/ui/qt/qt_pixmaps.py +++ b/pelita/ui/qt/qt_pixmaps.py @@ -1,10 +1,12 @@ +from ...graph import move_pos + from PyQt5 import QtCore, QtGui, QtWidgets -from PyQt5.QtCore import QRectF +from PyQt5.QtCore import QRectF, QPointF def generate_wall(maze, QWidget): pixmap = QtGui.QPixmap(QWidget.width() * 2, QWidget.height() * 2) - pixmap.fill(QtCore.Qt.transparent) + pixmap.fill(QtCore.Qt.white) pixmap.setDevicePixelRatio(2.0) universe_width = QWidget.universe.maze.width @@ -21,6 +23,79 @@ def generate_wall(maze, QWidget): blue_col = QtGui.QColor(94, 158, 217) red_col = QtGui.QColor(235, 90, 90) + # to draw the border, we start on an unvisited wall position, + # that is surrounded by at least one square of free space. + # we then follow the outline clockwise and counterclockwise + visited = set() + start = (0, 0) + position = start +# path = QtGui.QPainterPath(QPointF(position[0] + 0.2, position[1] + 0.2)) + + for position, value in maze.items(): + if position[0] < QWidget.universe.maze.width / 2: + #brush = QtGui.QBrush(blue_col, QtCore.Qt.Dense4Pattern) + painter.setPen(QtGui.QPen(blue_col, pen_size)) + else: + painter.setPen(QtGui.QPen(red_col, pen_size)) + if not value: continue + + rot_moves = [(0, [(-1, 0), (-1, -1), ( 0, -1)]), + (90, [( 0, -1), ( 1, -1), ( 1, 0)]), + (180, [( 1, 0), ( 1, 1), ( 0, 1)]), + (270, [( 0, 1), (-1, 1), (-1, 0)])] + + for rot, moves in rot_moves: + # we center on the middle point of the square + painter.save() + painter.translate(position[0] + 0.5, position[1] + 0.5) + painter.rotate(rot) + + wall_moves = [move for move in moves + if move_pos(position, move) in maze and maze[move_pos(position, move)]] + + left, topleft, top, *remainder = moves + + if left in wall_moves and top not in wall_moves: + painter.drawLine(QPointF(-0.5, -0.3), QPointF(0, -0.3)) + elif left in wall_moves and top in wall_moves and not topleft in wall_moves: + painter.drawArc(QRectF(-0.7, -0.7, 0.4, 0.4), 0 * 16, -90 * 16) + elif left in wall_moves and top in wall_moves and topleft in wall_moves: + pass + elif left not in wall_moves and top not in wall_moves: + painter.drawArc(QRectF(-0.3, -0.3, 0.6, 0.6), 90 * 16, 90 * 16) + elif left not in wall_moves and top in wall_moves: + painter.drawLine(QPointF(-0.3, -0.5), QPointF(-0.3, 0)) + + painter.restore() + +# if (0, -1) in wall_moves and not (1, -1) in wall_moves and (1, 0) in wall_moves: +# painter.drawArc(QRectF(position[0] + 0.7, position[1] + 0.3, 0.6, -0.6), 180 * 16, 90 * 16) +# elif (0, -1) in wall_moves and not (1, 0) in wall_moves and (0, 1) in wall_moves: +# painter.drawLine(QPointF(position[0] + 0.7, position[1] + 0), QPointF(position[0] + 0.7, position[1] + 1)) +# elif (0, -1) not in wall_moves and not (1, 0) in wall_moves and (0, 1) in wall_moves: +# painter.drawArc(QRectF(position[0] + 0.1, position[1] + 0.3, 0.6, 0.6), 16, 90 * 16) + +# visited.add(position) +# top_neighbor = move_pos(position, (0, -1)) +# bottom_neighbor = move_pos(position, (0, 1)) +# left_neighbor = move_pos(position, (-1, 0)) +# right_neighbor = move_pos(position, (1, 0)) +# print(top_neighbor, bottom_neighbor, left_neighbor, right_neighbor) +# if top_neighbor in maze and maze[top_neighbor] and not top_neighbor in visited: +# path.lineTo(top_neighbor[0] + 0.2, top_neighbor[1] + 0.8) +# position = top_neighbor +# elif right_neighbor in maze and maze[right_neighbor] and not right_neighbor in visited: +# path.lineTo(right_neighbor[0] + 0.2, right_neighbor[1] + 0.8) +# position = right_neighbor +# elif bottom_neighbor in maze and maze[bottom_neighbor] and not bottom_neighbor in visited: +# path.lineTo(bottom_neighbor[0] + 0.2, bottom_neighbor[1] + 0.8) +# position = bottom_neighbor +# elif left_neighbor in maze and maze[left_neighbor] and not left_neighbor in visited: +# path.lineTo(left_neighbor[0] + 0.2, left_neighbor[1] + 0.8) +# position = left_neighbor + +# painter.drawPath(path) + for position, wall in maze.items(): if wall: if position[0] < QWidget.universe.maze.width / 2: @@ -33,6 +108,6 @@ def generate_wall(maze, QWidget): inverted = painter.worldTransform().inverted() brush.setTransform(inverted[0]) painter.setBrush(brush) - painter.drawEllipse(QRectF(position[0] + 0.1, position[1] + 0.1, 0.8, 0.8)) +# painter.drawEllipse(QRectF(position[0] + 0.1, position[1] + 0.1, 0.8, 0.8)) return pixmap diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py index c85089d23..fde19b912 100644 --- a/pelita/ui/qt/qt_viewer.py +++ b/pelita/ui/qt/qt_viewer.py @@ -14,8 +14,8 @@ from PyQt5.QtWidgets import QWidget, QApplication, QMainWindow from PyQt5.QtCore import QPointF, QRectF -from pelita.graph import diff_pos -from pelita.datamodel import CTFUniverse +from ...graph import diff_pos +from ...datamodel import CTFUniverse _logger = logging.getLogger(__name__) _logger.setLevel(logging.DEBUG) @@ -59,9 +59,16 @@ def setupUi(self, MainWindow): self.centralwidget = QWidget(MainWindow) self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget) - self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 500, 22)) - MainWindow.setMenuBar(self.menubar) + menubar = QtWidgets.QMenuBar(None) +# self.menubar.setGeometry(QtCore.QRect(0, 0, 500, 22)) + fileMenu = menubar.addMenu('&File') + + impAct = QtWidgets.QAction('Import mail', MainWindow) + fileMenu.addAction(impAct) + + menubar.addMenu(fileMenu) + + MainWindow.setMenuBar(menubar) # self.statusbar = QtWidgets.QStatusBar(MainWindow) # MainWindow.setStatusBar(self.statusbar) QtCore.QMetaObject.connectSlotsByName(MainWindow) @@ -143,6 +150,7 @@ def paintEvent(self, event): from .qt_pixmaps import generate_wall self.wall_pm = generate_wall(self.universe.maze, self) painter.drawPixmap(0, 0, self.width(), self.height(), self.wall_pm) + del self.wall_pm universe_width = self.universe.maze.width universe_height = self.universe.maze.height From 7baa6b0e10c5eba031f631887633e4d34ccb96cb Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Mon, 12 Mar 2018 16:37:53 +0100 Subject: [PATCH 13/13] WIP --- pelita/ui/qt/qt_viewer.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/pelita/ui/qt/qt_viewer.py b/pelita/ui/qt/qt_viewer.py index fde19b912..138721ad6 100644 --- a/pelita/ui/qt/qt_viewer.py +++ b/pelita/ui/qt/qt_viewer.py @@ -83,7 +83,7 @@ def __init__(self, address, controller_address=None, if export: png_export_path = Path(export) if not png_export_path.is_dir(): - raise RuntimeError(f"Not a directory: {png_export_path}") + raise RuntimeError("Not a directory: {png_export_path}") self.png_export_path = png_export_path else: self.png_export_path = None @@ -92,7 +92,7 @@ def __init__(self, address, controller_address=None, self.exit_socket = self.context.socket(zmq.PAIR) exit_address = self.exit_socket.bind_to_random_port('tcp://127.0.0.1') - self.zmq_listener = ZMQListener(address, f'tcp://127.0.0.1:{exit_address}') + self.zmq_listener = ZMQListener(address, 'tcp://127.0.0.1:{}'.format(exit_address)) self.zmq_listener.message.connect(self.signal_received) QtCore.QTimer.singleShot(0, self.zmq_listener.start) @@ -167,6 +167,9 @@ def paint(pos): painter.drawArc(QRectF(pos[0] + 0.2, pos[1] + 0.2, 0.6, 0.6), 0, 5760) def paint_harvester(pos, color): + painter.save() + + direction = self.directions.get(bot.index) if not direction: direction = (0, 1) @@ -174,6 +177,18 @@ def paint_harvester(pos, color): rotation = math.degrees(cmath.phase(direction[0] - direction[1]*1j)) x, y = pos + painter.translate(x, y) +# painter.scale(0.5, 0.5) +# if bot.index == 0: +# painter.translate(0, 0) +# elif bot.index == 1: +# painter.translate(1, 0) +# elif bot.index == 2: +# painter.translate(0, 1) +# elif bot.index == 3: +# painter.translate(1, 1) + + x = y = 0 bounding_rect = QRectF(x, y, 1, 1) # bot body path = QtGui.QPainterPath(QPointF(x + 0.5, y + 0.5)) @@ -194,6 +209,8 @@ def paint_harvester(pos, color): elif direction == (-1, 0): # left painter.drawEllipse(QRectF(x + 0.6 - eye_size, y + 0.3 - eye_size, eye_size * 2, eye_size * 2)) + painter.restore() + def paint_destroyer(pos, color): x, y = pos bounding_rect = QRectF(x, y, 1, 1) @@ -281,7 +298,7 @@ def observe(self, observed): try: round_index = game_state['round_index'] bot_id = game_state['bot_id'] - file_name = f'pelita-{round_index}-{bot_id}.png' + file_name = 'pelita-{}-{}.png'.format(round_index, bot_id) self.grab().save(str(self.png_export_path / file_name)) except TypeError as e: