Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proof of concept for a minimal PyQt5 UI #460

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
9 changes: 6 additions & 3 deletions pelita/libpelita.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, export=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) ]
Expand All @@ -434,11 +436,12 @@ 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)]

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
Expand Down
10 changes: 7 additions & 3 deletions pelita/scripts/pelita_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -183,6 +184,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')
Expand Down Expand Up @@ -266,7 +269,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:
Expand Down Expand Up @@ -352,14 +355,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, 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)
Expand Down
55 changes: 55 additions & 0 deletions pelita/scripts/pelita_qtviewer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/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')
parser.add_argument('--export', type=str, metavar="FOLDER", help='png export path')

def main():
args = parser.parse_args()
viewer_args = {
'address': args.subscribe_sock,
'controller_address': args.controller_address,
'geometry': args.geometry,
'delay': args.delay,
'export': args.export
}

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()

2 changes: 1 addition & 1 deletion pelita/scripts/pelita_tkviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__':
Expand Down
113 changes: 113 additions & 0 deletions pelita/ui/qt/qt_pixmaps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@

from ...graph import move_pos

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QRectF, QPointF

def generate_wall(maze, QWidget):
pixmap = QtGui.QPixmap(QWidget.width() * 2, QWidget.height() * 2)
pixmap.fill(QtCore.Qt.white)
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)

# 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:
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
Loading