class ReadoutView(View): class Orientation(Enum): horizontal = 1 vertical = 2 def __init__(self, model): super().__init__(model) self.graphics_item = QGraphicsRectItem(0, 0, 9, 9) self.graphics_item.setBrush(Qt.black) inner_rect_item = QGraphicsRectItem(1, 1, 7, 7, self.graphics_item) inner_rect_item.setBrush(Qt.white) self.text_item = None self.orientation = ReadoutView.Orientation.horizontal def line(self): for child in self.graphics_item.childItems(): if isinstance(child, QGraphicsLineItem): return child.line() def line_item(self): for child in self.graphics_item.childItems(): if isinstance(child, QGraphicsLineItem): return child def init_text(self): width = self.graphics_item.boundingRect().width() height = self.graphics_item.boundingRect().height() self.text_item = QGraphicsTextItem(self.graphics_item) self.text_item.setPos(width / 2 + 2, -height)
class CplxItem(KineticsDisplayItem): defaultWidth = 10 defaultHeight = 10 name = constants.ITEM def __init__(self, *args, **kwargs): KineticsDisplayItem.__init__(self, *args, **kwargs) self.gobj = QGraphicsRectItem(0, 0, CplxItem.defaultWidth, CplxItem.defaultHeight, self) self.gobj.mobj = self.mobj self._conc = self.mobj.conc self._n = self.mobj.n doc = "Conc\t: " + str(self._conc) + "\nn\t: " + str(self._n) self.gobj.setToolTip(doc) def updateValue(self, gobj): self._gobj = gobj if (isinstance(self._gobj, moose.PoolBase)): self._conc = self.mobj.conc self._n = self.mobj.n doc = "Conc\t: " + str(self._conc) + "\nn\t: " + str(self._n) self.gobj.setToolTip(doc) def setDisplayProperties(self, x, y, textcolor, bgcolor): """Set the display properties of this item.""" self.setGeometry(self.gobj.boundingRect().width() / 2, self.gobj.boundingRect().height(), self.gobj.boundingRect().width(), self.gobj.boundingRect().height()) self.setFlag(QGraphicsItem.ItemIsMovable, False) def refresh(self, scale): defaultWidth = CplxItem.defaultWidth * scale defaultHeight = CplxItem.defaultHeight * scale self.gobj.setRect(0, 0, defaultWidth, defaultHeight)
def label_maker(node, label): """Form correctly formatted leaf label.""" face = QGraphicsTextItem() face.setHtml(label) # Create parent RectItem with TextItem in center fbox = face.boundingRect() rect = QGraphicsRectItem(0, 0, fbox.width(), fbox.height()) rbox = rect.boundingRect() face.setPos(rbox.x(), rbox.center().y() - fbox.height() / 2) # Remove border rect.setPen(QPen(QtCore.Qt.NoPen)) # Set as parent item so DynamicItemFace can use .rect() method face.setParentItem(rect) return rect
class SvgView(QGraphicsView): Native, OpenGL, Image = range(3) def __init__(self, parent=None): super(SvgView, self).__init__(parent) self.renderer = SvgView.Native self.svgItem = None self.backgroundItem = None self.outlineItem = None self.image = QImage() self.setScene(QGraphicsScene(self)) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setDragMode(QGraphicsView.ScrollHandDrag) self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) # Prepare background check-board pattern. tilePixmap = QPixmap(64, 64) tilePixmap.fill(Qt.white) tilePainter = QPainter(tilePixmap) color = QColor(220, 220, 220) tilePainter.fillRect(0, 0, 32, 32, color) tilePainter.fillRect(32, 32, 32, 32, color) tilePainter.end() self.setBackgroundBrush(QBrush(tilePixmap)) def drawBackground(self, p, rect): p.save() p.resetTransform() p.drawTiledPixmap(self.viewport().rect(), self.backgroundBrush().texture()) p.restore() def openFile(self, svg_file): if not svg_file.exists(): return s = self.scene() if self.backgroundItem: drawBackground = self.backgroundItem.isVisible() else: drawBackground = False if self.outlineItem: drawOutline = self.outlineItem.isVisible() else: drawOutline = True s.clear() self.resetTransform() self.svgItem = QGraphicsSvgItem(svg_file.fileName()) self.svgItem.setFlags(QGraphicsItem.ItemClipsToShape) self.svgItem.setCacheMode(QGraphicsItem.NoCache) self.svgItem.setZValue(0) self.backgroundItem = QGraphicsRectItem(self.svgItem.boundingRect()) self.backgroundItem.setBrush(Qt.white) self.backgroundItem.setPen(QPen(Qt.NoPen)) self.backgroundItem.setVisible(drawBackground) self.backgroundItem.setZValue(-1) self.outlineItem = QGraphicsRectItem(self.svgItem.boundingRect()) outline = QPen(Qt.black, 2, Qt.DashLine) outline.setCosmetic(True) self.outlineItem.setPen(outline) self.outlineItem.setBrush(QBrush(Qt.NoBrush)) self.outlineItem.setVisible(drawOutline) self.outlineItem.setZValue(1) s.addItem(self.backgroundItem) s.addItem(self.svgItem) s.addItem(self.outlineItem) s.setSceneRect(self.outlineItem.boundingRect().adjusted( -10, -10, 10, 10)) def setRenderer(self, renderer): self.renderer = renderer if self.renderer == SvgView.OpenGL: if QGLFormat.hasOpenGL(): self.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers))) else: self.setViewport(QWidget()) def setHighQualityAntialiasing(self, highQualityAntialiasing): if QGLFormat.hasOpenGL(): self.setRenderHint(QPainter.HighQualityAntialiasing, highQualityAntialiasing) def setViewBackground(self, enable): if self.backgroundItem: self.backgroundItem.setVisible(enable) def setViewOutline(self, enable): if self.outlineItem: self.outlineItem.setVisible(enable) def paintEvent(self, event): if self.renderer == SvgView.Image: if self.image.size() != self.viewport().size(): self.image = QImage(self.viewport().size(), QImage.Format_ARGB32_Premultiplied) imagePainter = QPainter(self.image) QGraphicsView.render(self, imagePainter) imagePainter.end() p = QPainter(self.viewport()) p.drawImage(0, 0, self.image) else: super(SvgView, self).paintEvent(event) def wheelEvent(self, event): factor = pow(1.2, event.delta() / 240.0) self.scale(factor, factor) event.accept()
class SvgView(QGraphicsView): Native, OpenGL, Image = range(3) def __init__(self, parent=None): super(SvgView, self).__init__(parent) self.renderer = SvgView.Native self.svgItem = None self.backgroundItem = None self.outlineItem = None self.image = QImage() self.setScene(QGraphicsScene(self)) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setDragMode(QGraphicsView.ScrollHandDrag) self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) # Prepare background check-board pattern. tilePixmap = QPixmap(64, 64) tilePixmap.fill(Qt.white) tilePainter = QPainter(tilePixmap) color = QColor(220, 220, 220) tilePainter.fillRect(0, 0, 32, 32, color) tilePainter.fillRect(32, 32, 32, 32, color) tilePainter.end() self.setBackgroundBrush(QBrush(tilePixmap)) def drawBackground(self, p, rect): p.save() p.resetTransform() p.drawTiledPixmap(self.viewport().rect(), self.backgroundBrush().texture()) p.restore() def openFile(self, svg_file): if not svg_file.exists(): return s = self.scene() if self.backgroundItem: drawBackground = self.backgroundItem.isVisible() else: drawBackground = False if self.outlineItem: drawOutline = self.outlineItem.isVisible() else: drawOutline = True s.clear() self.resetTransform() self.svgItem = QGraphicsSvgItem(svg_file.fileName()) self.svgItem.setFlags(QGraphicsItem.ItemClipsToShape) self.svgItem.setCacheMode(QGraphicsItem.NoCache) self.svgItem.setZValue(0) self.backgroundItem = QGraphicsRectItem(self.svgItem.boundingRect()) self.backgroundItem.setBrush(Qt.white) self.backgroundItem.setPen(QPen(Qt.NoPen)) self.backgroundItem.setVisible(drawBackground) self.backgroundItem.setZValue(-1) self.outlineItem = QGraphicsRectItem(self.svgItem.boundingRect()) outline = QPen(Qt.black, 2, Qt.DashLine) outline.setCosmetic(True) self.outlineItem.setPen(outline) self.outlineItem.setBrush(QBrush(Qt.NoBrush)) self.outlineItem.setVisible(drawOutline) self.outlineItem.setZValue(1) s.addItem(self.backgroundItem) s.addItem(self.svgItem) s.addItem(self.outlineItem) s.setSceneRect(self.outlineItem.boundingRect().adjusted(-10, -10, 10, 10)) def setRenderer(self, renderer): self.renderer = renderer if self.renderer == SvgView.OpenGL: if QGLFormat.hasOpenGL(): self.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers))) else: self.setViewport(QWidget()) def setHighQualityAntialiasing(self, highQualityAntialiasing): if QGLFormat.hasOpenGL(): self.setRenderHint(QPainter.HighQualityAntialiasing, highQualityAntialiasing) def setViewBackground(self, enable): if self.backgroundItem: self.backgroundItem.setVisible(enable) def setViewOutline(self, enable): if self.outlineItem: self.outlineItem.setVisible(enable) def paintEvent(self, event): if self.renderer == SvgView.Image: if self.image.size() != self.viewport().size(): self.image = QImage(self.viewport().size(), QImage.Format_ARGB32_Premultiplied) imagePainter = QPainter(self.image) QGraphicsView.render(self, imagePainter) imagePainter.end() p = QPainter(self.viewport()) p.drawImage(0, 0, self.image) else: super(SvgView, self).paintEvent(event) def wheelEvent(self, event): print(event.angleDelta().y()) factor = pow(1.2, event.angleDelta().y() / 240.0) self.scale(factor, factor) event.accept()
class Scene_Base(QGraphicsScene): def __init__(self, window): super().__init__() self.setSceneRect(0, 0, window.game_width, window.game_height) self.window = window self.init_fields() self.create_fade() self.dialog_box = None self.setup() def init_fields(self): self.actions = [] self.current_action = -1 self.dialog_box = None self.target_text = None self.current_dialog_text = None self.current_dialog_text_offset = 0 self.background = None self.fading_box = None self.fade_dir = 0 self.next_scene = None self.buttons = [] self.current_id = 1 self.texts = {} self.images = {} self.moving_images = [] self.updatable_images = [] self.vaxx = None self.entry = None self.pathogen = None self.tempPic = None def create_fade(self, opc=1): self.fading_box = QGraphicsRectItem(0, 0, self.width(), self.height()) self.fading_box.setBrush(QColor(0, 0, 0)) self.fading_box.setOpacity(opc) self.fading_box.setZValue(100) self.addItem(self.fading_box) # ============================================== # * Setup # # Overwrite with child classes to add actions. # ============================================== def setup(self): pass # ============================================== # * Update # # Updates the scene. Handles input, waiting, etc. # ============================================== def update(self): self.update_fade() self.update_dialog() self.update_dialog_text() self.update_moving_images() self.update_animated_images() def update_fade(self): if self.fading_box is not None: if self.fade_dir == 0 and self.fading_box.opacity() > 0: self.fading_box.setOpacity(self.fading_box.opacity() - 0.02) if self.fading_box.opacity() <= 0: self.removeItem(self.fading_box) self.fading_box = None elif self.fade_dir == 1 and self.fading_box.opacity() < 1: self.fading_box.setOpacity(self.fading_box.opacity() + 0.02) if self.fading_box.opacity() >= 1: if self.next_scene is not None: self.window.goto_scene(self.next_scene) else: self.actually_close_game() def update_dialog(self): if self.dialog_box is not None: if self.dialog_box.opacity() < 0.5: self.dialog_box.setOpacity(self.dialog_box.opacity() + 0.04) if self.dialog_box.opacity() >= 0.5: self.dialog_box.setOpacity(0.5) if self.actions[0][0] == 0: self.actually_show_dialog(self.actions[0]) def update_dialog_text(self): if self.current_dialog_text is not None: curr_text = self.current_dialog_text.toPlainText() if curr_text != self.target_text: if self.current_dialog_text_offset < 3: self.current_dialog_text_offset += 1 else: self.current_dialog_text_offset = 0 self.current_dialog_text.setPlainText(self.target_text[:len(curr_text) + 1]) def update_moving_images(self): if len(self.moving_images) > 0: index = 0 new_data = [] for image in self.moving_images: pic_id = image[0] item = self.images[pic_id] item.setX(item.x() + image[1]) item.setY(item.y() + image[2]) image[3] -= 1 if image[3] > 0: new_data.append(image) elif image[4] is not None: image[4]() index += 1 self.moving_images = new_data def update_animated_images(self): if len(self.updatable_images) > 0: index = 0 for image in self.updatable_images: image[2] += 1 if image[2] > image[4]: image[2] = 0 image[3] += 1 if image[3] >= len(image[1]): image[3] = 0 image[0].setPixmap(QPixmap(image[1][image[3]])) # ============================================== # * Mouse Events # # Handle mouse input. # ============================================== def mouseMoveEvent(self, mouseEvent): pass def mousePressEvent(self, mouseEvent): self.when_mouse_pressed(mouseEvent) def mouseDoubleClickEvent(self, mouseEvent): self.when_mouse_pressed(mouseEvent) def when_mouse_pressed(self, mouseEvent): if self.current_action == 0 and mouseEvent.button() == 1: if self.current_dialog_text is not None: if self.current_dialog_text.toPlainText() != self.target_text: self.current_dialog_text.setPlainText(self.target_text) else: self.removeItem(self.current_dialog_text) self.call_next_action() else: super(Scene_Base, self).mousePressEvent(mouseEvent) # ============================================== # * Action Management # ============================================== def finish_action(self): if len(self.actions) > 0: self.actions.pop(0) else: self.wait_for_button_press() def check_if_first(self): if len(self.actions) == 1: self.perform_next_action() def perform_next_action(self): if len(self.actions) > 0: action = self.actions[0] self.current_action = action_type = action[0] if action_type is -1: self.actually_hide_dialog_box() elif action_type is 0: self.actually_show_dialog(action) elif action_type is 1: self.actually_show_button(action) elif action_type is 2: self.actually_set_background(action) elif action_type is 3: self.actually_goto_scene(action) elif action_type is 4: self.actually_close_game() elif action_type is 5: self.actually_play_song(action) elif action_type is 6: self.actually_wait_for_button_press() elif action_type is 7: self.actually_remove_all_buttons() elif action_type is 8: self.actually_show_text(action) elif action_type is 9: self.actually_hide_text(action) elif action_type is 10: self.actually_show_image(action) elif action_type is 11: self.actually_hide_image(action) elif action_type is 12: self.actually_move_image(action) elif action_type is 50: self.actually_play_sound(action) def call_next_action(self): self.finish_action() self.perform_next_action() # ============================================== # * Actual Implementations # ============================================== def actually_show_dialog(self, data): if self.dialog_box is None: self.create_dialog_box() else: self.current_dialog_text = QGraphicsTextItem() self.current_dialog_text.setZValue(20) self.current_dialog_text.setDefaultTextColor(QColor(255, 255, 255)) temp_font = self.current_dialog_text.font() temp_font.setPointSize(data[2]) self.current_dialog_text.setFont(temp_font) self.addItem(self.current_dialog_text) self.current_dialog_text.setX(self.dialog_box.x() + 10) self.current_dialog_text.setY(self.dialog_box.y() + 10) self.current_dialog_text.setTextWidth(self.dialog_box.boundingRect().width() - 20) self.target_text = data[1] self.current_dialog_text_offset = 0 def create_dialog_box(self): self.dialog_box = QGraphicsRectItem(0, 0, self.width() - 20, self.height() / 4) self.dialog_box.setBrush(QColor(0, 0, 0)) self.dialog_box.setX(10) self.dialog_box.setY(self.height() - self.dialog_box.boundingRect().height() - 10) self.dialog_box.setZValue(15); self.dialog_box.setOpacity(0) self.addItem(self.dialog_box) def actually_hide_dialog_box(self): if self.dialog_box is not None: self.removeItem(self.dialog_box) self.dialog_box = None def actually_show_button(self, data): button = Button(self, data[3], data[4], data[5], data[6], data[7], data[8], data[9]) button.setX(data[1]) button.setY(data[2]) self.buttons.append(button) self.addItem(button) self.call_next_action() def actually_set_background(self, data): if self.background is not None: self.removeItem(self.background) self.background = None self.background = QGraphicsPixmapItem(QPixmap(data[1]).scaled(self.window.game_width, self.window.game_height)) self.background.setZValue(-10) self.addItem(self.background) self.call_next_action() def actually_goto_scene(self, data): self.next_scene = data[1] self.fade_dir = 1 self.create_fade(0) def actually_close_game(self): self.window.close_game() def actually_play_song(self, data): AudioPlayer.play_song(data[1]) self.call_next_action() def actually_wait_for_button_press(self): self.current_action = -1 def actually_remove_all_buttons(self): for b in self.buttons: self.removeItem(b) self.buttons = [] self.call_next_action() def actually_show_text(self, data): text_item = QGraphicsTextItem() text_item.setZValue(5) if len(data) >= 8 and data[7] != None: text_item.setDefaultTextColor(QColor(data[7])) else: text_item.setDefaultTextColor(QColor("#FFFFFF")) text_item.setX(data[2]) text_item.setY(data[3]) text_item.setTextWidth(data[4]) text_item.setPlainText(data[5]) temp_font = text_item.font() temp_font.setPointSize(data[6]) text_item.setFont(temp_font) self.addItem(text_item) self.texts[data[1]] = text_item self.call_next_action() def actually_hide_text(self, data): self.removeItem(self.texts[data[1]]) self.texts[data[1]] = None self.call_next_action() def actually_show_image(self, data): image = None if isinstance(data[2], list): image = QGraphicsPixmapItem(QPixmap(data[2][0])) self.updatable_images.append([image, data[2], 0, 0, data[5]]) else: image = QGraphicsPixmapItem(QPixmap(data[2])) image.setX(data[3]) image.setY(data[4]) image.setZValue(0) self.addItem(image) self.images[data[1]] = image self.call_next_action() def actually_hide_image(self, data): self.removeItem(self.images[data[1]]) self.images[data[1]] = None self.call_next_action() def actually_move_image(self, data): image_id = data[1] image = self.images[image_id] duration = data[4] x_speed = (data[2] - image.x()) / duration y_speed = (data[3] - image.y()) / duration callback = self.call_next_action if data[5] else None image_data = [image_id, x_speed, y_speed, duration, callback] self.moving_images.append(image_data) if not data[5]: self.call_next_action() def actually_play_sound(self, data): AudioPlayer.play_sound_effect(data[1]) self.call_next_action() # ============================================== # * Setup Calls # ============================================== def add_call(self, data): self.actions.append(data) self.check_if_first() # ============================================== # * Extended Calls # ============================================== # ============================================== # Adds a dialog to the game. # # Ex: # self.add_dialog("Hello World!") # ============================================== def add_dialog(self, msg, fontSize=20): self.add_call([0, msg, fontSize]) # ============================================== # Hides the dialog box # # Ex: # self.hide_dialog_box() # ============================================== def hide_dialog_box(self): self.add_call([-1]) # ============================================== # Adds a button to the game. # After adding all buttons, be sure to call "self.wait_for_button_press". # Check scenes/scene_titlescreen.py for example of "font" and "buttonColors" # # Ex: # self.add_button(30, 30, 200, 200, "My Button!", self.another_function) # ============================================== def add_button(self, x, y, w, h, name, action, font=None, buttonColors=None, textColors=None): self.add_call([1, x, y, w, h, name, font, buttonColors, textColors, action]) # ============================================== # Sets the current background. # # Ex: # self.set_background("images/Background1.png") # ============================================== def set_background(self, path): self.add_call([2, path]) # ============================================== # Changes the game to the provided scene. # # Ex: # self.goto_scene(scenes.my_other_scene.My_Other_Scene) # ============================================== def goto_scene(self, scene): self.add_call([3, scene]) # ============================================== # Closes the game. # # Ex: # self.close_game() # ============================================== def close_game(self): self.add_call([4]) # ============================================== # Plays a song. # # Ex: # self.play_song("audio/testmusic2.mp3") # ============================================== def play_song(self, path): self.add_call([5, path]) # ============================================== # Plays a sound effect once. # # Ex: # self.play_sound("audio/testmusic2.mp3") # ============================================== def play_sound(self, path): self.add_call([50, path]) # ============================================== # Once all buttons are created, this will wait for the player to press one. # # Ex: # self.wait_for_button_press() # ============================================== def wait_for_button_press(self): self.add_call([6]) # ============================================== # Removes all buttons from the screen. # # Ex: # self.remove_all_buttons() # ============================================== def remove_all_buttons(self): self.add_call([7]) # ============================================== # Based on the value provided, there is a chance it will return True. # # Ex: # if self.generate_random_chance(30): # # there is a 30% chance of this happening # else: # # there is a 70% chance of this happening # ============================================== def generate_random_chance(self, val): return random.randint(0, 100) <= val # ============================================== # Shows text on the screen (not in dialog). # This function returns an ID that can be used in self.hide_text. # # Ex: # text_id = self.show_text(10, 10, 200, "Hello Screen!", 40, "#FF44CC") # ============================================== def show_text(self, x, y, w, text, size=30, color=None): new_id = self.current_id self.current_id += 1 self.add_call([8, new_id, x, y, w, text, size, color]) return new_id # ============================================== # Hides the text connected to the ID. # # Ex: # self.hide_text(text_id) # ============================================== def hide_text(self, text_id): self.add_call([9, text_id]) # ============================================== # Shows a picture on the screen. # This function returns an ID that can be used in other functions. # # Ex: # pic_id = self.show_picture("images/TestImage1.png", 30, 30) # # ---------------------------------------------- # # Using an array of strings will create an animation. # In that case, the last argument is the frame-change frequency of the animation. # # Ex: # pic_id = self.show_picture(["images/p1_frame_0.png", "images/p1_frame_1.png"], 100, 100, 30) # ============================================== def show_picture(self, path, x, y, animation_speed=20): new_id = self.current_id self.current_id += 1 self.add_call([10, new_id, path, x, y, animation_speed]) return new_id # ============================================== # Removes the picture connected to the provided ID. # # Ex: # self.hide_picture(pic_id) # ============================================== def hide_picture(self, image_id): self.add_call([11, image_id]) # ============================================== # Moves the picture to new coordinates over a duration of time. # # Ex: # self.move_picture(pic_id, 90, 30, 120) # ============================================== def move_picture(self, image_id, x, y, duration, wait_until_finished=True): self.add_call([12, image_id, x, y, duration, wait_until_finished]) #=============================================== # Sets the vaccination status # True/False, 50% chance #=============================================== def set_vaxx(self): if self.generate_random_chance(50): Scene_Base.vaxx = True else: Scene_Base.vaxx = False #=============================================== # Sets the entry point # 0 is for blood (cut) # 1 is for stomach (mouth) #=============================================== def set_entry(self): if self.generate_random_chance(50): Scene_Base.entry = 0 else: Scene_Base.entry = 1 # ============================================== # Gets and sets global values # # Ex: # self.set_value("Health", 100) # # self.add_value("Health", -1) # # if self.get_value("Health") <= 30: # self.add_dialog("Player is less than 30 health!") # ============================================== def set_value(self, name, value): Scene_Base.GLOBAL_VARS[name] = value def add_value(self, name, value): Scene_Base.GLOBAL_VARS[name] += value def get_value(self, name): return Scene_Base.GLOBAL_VARS[name]
def refresh(self): if not self._mdlPlots or not self._mdlOutline or not self._mdlCharacter: return if not self.isVisible(): return LINE_HEIGHT = 18 SPACING = 3 TEXT_WIDTH = self.sldTxtSize.value() CIRCLE_WIDTH = 10 LEVEL_HEIGHT = 12 s = self.scene s.clear() # Get Max Level (max depth) root = self._mdlOutline.rootItem def maxLevel(item, level=0, max=0): if level > max: max = level for c in item.children(): m = maxLevel(c, level + 1) if m > max: max = m return max MAX_LEVEL = maxLevel(root) # Get the list of tracked items (array of references) trackedItems = [] if self.actPlots.isChecked(): trackedItems += self.plotReferences() if self.actCharacters.isChecked(): trackedItems += self.charactersReferences() ROWS_HEIGHT = len(trackedItems) * (LINE_HEIGHT + SPACING) fm = QFontMetrics(s.font()) max_name = 0 for ref in trackedItems: name = references.title(ref) max_name = max(fm.width(name), max_name) TITLE_WIDTH = max_name + 2 * SPACING # Add Folders and Texts outline = OutlineRect(0, 0, 0, ROWS_HEIGHT + SPACING + MAX_LEVEL * LEVEL_HEIGHT) s.addItem(outline) outline.setPos(TITLE_WIDTH + SPACING, 0) refCircles = [ ] # a list of all references, to be added later on the lines # A Function to add a rect with centered elided text def addRectText(x, w, parent, text="", level=0, tooltip=""): deltaH = LEVEL_HEIGHT if level else 0 r = OutlineRect(0, 0, w, parent.rect().height() - deltaH, parent, title=text) r.setPos(x, deltaH) txt = QGraphicsSimpleTextItem(text, r) f = txt.font() f.setPointSize(8) fm = QFontMetricsF(f) elidedText = fm.elidedText(text, Qt.ElideMiddle, w) txt.setFont(f) txt.setText(elidedText) txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) txt.setY(0) return r # A function to returns an item's width, by counting its children def itemWidth(item): if item.isFolder(): r = 0 for c in item.children(): r += itemWidth(c) return r or TEXT_WIDTH else: return TEXT_WIDTH def listItems(item, rect, level=0): delta = 0 for child in item.children(): w = itemWidth(child) if child.isFolder(): parent = addRectText(delta, w, rect, child.title(), level, tooltip=child.title()) parent.setToolTip( references.tooltip(references.textReference( child.ID()))) listItems(child, parent, level + 1) else: rectChild = addRectText(delta, TEXT_WIDTH, rect, "", level, tooltip=child.title()) rectChild.setToolTip( references.tooltip(references.textReference( child.ID()))) # Find tracked references in that scene (or parent folders) for ref in trackedItems: result = [] # Tests if POV scenePOV = False # Will hold true of character is POV of the current text, not containing folder if references.type(ref) == references.CharacterLetter: ID = references.ID(ref) c = child while c: if c.POV() == ID: result.append(c.ID()) if c == child: scenePOV = True c = c.parent() # Search in notes/references c = child while c: result += references.findReferencesTo( ref, c, recursive=False) c = c.parent() if result: ref2 = result[0] # Create a RefCircle with the reference c = RefCircle(TEXT_WIDTH / 2, -CIRCLE_WIDTH / 2, CIRCLE_WIDTH, ID=ref2, important=scenePOV) # Store it, with the position of that item, to display it on the line later on refCircles.append( (ref, c, rect.mapToItem(outline, rectChild.pos()))) delta += w listItems(root, outline) OUTLINE_WIDTH = itemWidth(root) # Add Tracked items i = 0 itemsRect = s.addRect(0, 0, 0, 0) itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING) # Set of colors for plots (as long as they don't have their own colors) colors = [ "#D97777", "#AE5F8C", "#D9A377", "#FFC2C2", "#FFDEC2", "#D2A0BC", "#7B0F0F", "#7B400F", "#620C3D", "#AA3939", "#AA6C39", "#882D61", "#4C0000", "#4C2200", "#3D0022", ] for ref in trackedItems: if references.type(ref) == references.CharacterLetter: color = self._mdlCharacter.getCharacterByID( references.ID(ref)).color() else: color = QColor(colors[i % len(colors)]) # Rect r = QGraphicsRectItem(0, 0, TITLE_WIDTH, LINE_HEIGHT, itemsRect) r.setPen(QPen(Qt.NoPen)) r.setBrush(QBrush(color)) r.setPos(0, i * LINE_HEIGHT + i * SPACING) r.setToolTip(references.tooltip(ref)) i += 1 # Text name = references.title(ref) txt = QGraphicsSimpleTextItem(name, r) txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) # Line line = PlotLine(0, 0, OUTLINE_WIDTH + SPACING, 0) line.setPos(TITLE_WIDTH, r.mapToScene(r.rect().center()).y()) s.addItem(line) line.setPen(QPen(color, 5)) line.setToolTip(references.tooltip(ref)) # We add the circles / references to text, on the line for ref2, circle, pos in refCircles: if ref2 == ref: circle.setParentItem(line) circle.setPos(pos.x(), 0) # self.view.fitInView(0, 0, TOTAL_WIDTH, i * LINE_HEIGHT, Qt.KeepAspectRatioByExpanding) # KeepAspectRatio self.view.setSceneRect(0, 0, 0, 0)
class TextInputWidget(QGraphicsObject): input_entered = pyqtSignal(str) input_cancelled = pyqtSignal(str) font = QFont('monospace', 16) def __init__(self, mode: str = 'number', label: str = '', getter=None, setter=None, parser=str, validator=None, parent: Optional[QGraphicsItem] = None): QGraphicsObject.__init__(self, parent) self.getter: Callable[[], str] = getter self.setter: Callable[[str], None] = setter self.parser: Callable[[str], Any] = parser self.validator: Callable[[Any], bool] = validator self.accept_button = Button("btn_accept", "Accept", parent=self) self.accept_button.set_base_color([ButtonColor.GREEN]) self.accept_button.clicked.connect(self.handle_input_accepted) self.cancel_button = Button("btn_cancel", "Cancel", parent=self) self.cancel_button.set_base_color([ButtonColor.RED]) self.cancel_button.clicked.connect( lambda: self.input_cancelled.emit(self.text_field.toPlainText())) self.accept_button.set_height(12) self.cancel_button.set_height(12) self.hor_padding = 2 self.background_rect = QGraphicsRectItem(parent=self) self.background_rect.setBrush(QBrush(QColor(50, 50, 50, 200))) self.text_field = NumberInputItem(self) self.text_field.setFont(self.font) self.text_field.setTextInteractionFlags(Qt.TextEditable) self.text_field.setDefaultTextColor(Qt.white) self.text_field.text_changed.connect(self.adjust_layout) self.text_field.set_is_number_mode(mode == 'number') if self.getter is not None: self.text_field.setPlainText(str(self.getter())) self.adjust_layout() self.text_field.accepted.connect( lambda: self.accept_button.click_button(artificial_emit=True)) self.text_field.cancelled.connect( lambda: self.cancel_button.click_button(artificial_emit=True)) self.text_label = QGraphicsTextItem(self) self.text_label.setFont(self.font) self.text_label.setPlainText(label) self.text_label.setDefaultTextColor(Qt.white) self.setVisible(False) def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: Optional[QWidget] = ...) -> None: pass def boundingRect(self) -> QRectF: return self.background_rect.boundingRect().united(self.accept_button.boundingRect())\ .united(self.cancel_button.boundingRect()) def adjust_layout(self): self.accept_button.fit_to_contents() self.cancel_button.fit_to_contents() total_width = 3 * self.hor_padding + self.text_field.boundingRect().width() \ + self.text_label.boundingRect().width() total_width = max( total_width, 3 * self.hor_padding + self.accept_button.boundingRect().width() + self.cancel_button.boundingRect().width()) total_height = self.text_field.boundingRect().height() + self.accept_button.boundingRect().height() \ + 3 * self.hor_padding self.background_rect.setRect(0, 0, total_width, total_height) self.setTransformOriginPoint( QPointF(0.5 * total_width, 0.5 * total_height)) self.text_label.setPos(-0.5 * total_width + self.hor_padding, -0.5 * total_height + self.hor_padding) self.text_field.setPos( self.hor_padding + self.text_label.pos().x() + self.text_label.boundingRect().width(), self.text_label.pos().y()) self.background_rect.setPos(-0.5 * total_width, -0.5 * total_height) self.accept_button.set_width( (total_width - 3 * self.hor_padding) * 0.5) self.cancel_button.set_width( (total_width - 3 * self.hor_padding) * 0.5) self.accept_button.setPos(-0.5 * total_width + self.hor_padding, 2 * self.hor_padding) self.cancel_button.setPos(self.accept_button.pos() + QPointF( self.hor_padding + self.accept_button.boundingRect().width(), 0.0)) self.accept_button.set_disabled( len(self.text_field.toPlainText()) == 0) if self.validator is not None: try: value = self.parser(self.text_field.toPlainText()) is_valid = self.validator(value) except ValueError: is_valid = False self.text_field.set_is_valid(is_valid) self.accept_button.set_disabled(not is_valid) self.update() def get_value(self) -> str: return self.text_field.toPlainText() def set_value(self, text: str): self.text_field.setPlainText(text) self.adjust_layout() self.set_focus() def set_focus(self): self.text_field.setFocus(Qt.PopupFocusReason) cursor = self.text_field.textCursor() cursor.movePosition(QTextCursor.EndOfLine) self.text_field.setTextCursor(cursor) def set_getter_setter_parser_validator(self, getter: Callable[[], Any], setter: Callable[[Any], None], parser: Callable[[str], Any], validator: Callable[[Any], bool]): self.getter = getter self.setter = setter self.parser = parser self.validator = validator if self.getter is not None: self.text_field.setPlainText(str(self.getter())) self.adjust_layout() def set_label(self, label: str): self.text_label.setPlainText(label) self.adjust_layout() def handle_input_accepted(self): self.setter(self.parser(self.text_field.toPlainText())) self.input_entered.emit(self.text_field.toPlainText()) def handle_input_cancelled(self): self.input_cancelled.emit(str(self.getter()))
class GameArea(QWidget): #constants of widget RATIO_WITH_SHIPLIST = 11 / 14 RATIO_WITHOUT_SHIPLIST = 1 EPS = 0.3 #signals shipPlaced = pyqtSignal(Ship) def __init__(self, parent=None): super(GameArea, self).__init__(parent) self.__ui = Ui_GameArea() self.__ui.setupUi(self) self.__ratio = self.RATIO_WITH_SHIPLIST self.__scaleFactor = 1 self.controller = Controller() self.controller._accept = self.__accept self.controller._decline = self.__decline self.__gameModel = None self.__shipList = { "boat": ShipListItem(length=1, name="boat", count=4), "destroyer": ShipListItem(length=2, name="destroyer", count=3), "cruiser": ShipListItem(length=3, name="cruiser", count=2), "battleship": ShipListItem(length=4, name="battleship", count=1), } # resources self.__cellImages = {"intact": None, "hit": None, "miss": None} self.__sprites = {"explosion": None, "splash": None} self.__originalTileSize = 0 self.tileSize = 0 self.__shipListImage = QImage() self.__counterImage = QImage() # drawing items self.__placedShips = [] self.__field = [QGraphicsPixmapItem() for _ in range(100)] self.__letters = [QGraphicsTextItem() for _ in range(10)] self.__numbers = [QGraphicsTextItem() for _ in range(10)] for i in range(10): self.__letters[i].setDefaultTextColor(QColor.fromRgb(0, 0, 0)) self.__numbers[i].setDefaultTextColor(QColor.fromRgb(0, 0, 0)) self.__spriteAnimations = [] self.__shipListItem = QGraphicsPixmapItem() self.__ghostShip = QGraphicsPixmapItem( ) # data: 0 - rotation; 1 - ShipListItem self.__placer = QGraphicsRectItem() self.__dragShip = False self.__targetPixmap = None self.__targets = [] self.__scanEffect = None # prepare Qt objects self.__scene = QGraphicsScene() self.__loadResources() self.__initGraphicsView() self.__adjustedToSize = 0 def serviceModel(self, game_model): self.removeModel() self.__gameModel = game_model self.__gameModel.shipKilled.connect(self.__shootAround) def removeModel(self): if self.__gameModel: self.shipKilled.disconnect() self.__gameModel = None def __loadResources(self): if DEBUG_RESOURCE: resourcesPath = os.path.join(os.path.dirname(__file__), DEBUG_RESOURCE) else: resourcesPath = Environment.Resources.path() first = True for imageName in self.__cellImages: image = QImage( os.path.join(resourcesPath, "img", "cells", f"{imageName}.png")) if first: first = False self.__originalTileSize = min(image.width(), image.height()) else: self.__originalTileSize = min(self.__originalTileSize, image.width(), image.height()) self.__cellImages[imageName] = image for spriteName in self.__sprites: image = QImage( os.path.join(resourcesPath, "img", "cells", f"{spriteName}.png")) self.__sprites[spriteName] = image for shipName, ship in self.__shipList.items(): ship.image = QImage( os.path.join(resourcesPath, "img", "ships", f"{shipName}.png")) self.__shipListImage = QImage( os.path.join(resourcesPath, "img", "backgrounds", "shiplist.png")) self.__counterImage = QImage( os.path.join(resourcesPath, "img", "miscellaneous", "shipcounter.png")) self.__targetPixmap = QPixmap( os.path.join(resourcesPath, "img", "cells", "target.png")) def __initGraphicsView(self): self.__ui.graphicsView.setScene(self.__scene) self.__ui.graphicsView.viewport().installEventFilter(self) self.__ui.graphicsView.setRenderHints( QPainter.RenderHint.HighQualityAntialiasing | QPainter.RenderHint.TextAntialiasing | QPainter.RenderHint.SmoothPixmapTransform) self.__ui.graphicsView.horizontalScrollBar().blockSignals(True) self.__ui.graphicsView.verticalScrollBar().blockSignals(True) for cell in self.__field: cell.setData(0, "intact") pixmap = QPixmap.fromImage(self.__cellImages["intact"]) cell.setPixmap(pixmap) cell.setTransformationMode( Qt.TransformationMode.SmoothTransformation) self.__scene.addItem(cell) ca_letters = ["А", "Б", "В", "Г", "Д", "Е", "Ж", "З", "И", "К"] # ca_letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] ca_numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] font = QFont("Roboto") font.setPixelSize(int(self.__originalTileSize * 0.8)) for i in range(10): self.__letters[i].setPlainText(ca_letters[i]) self.__letters[i].setFont(font) self.__numbers[i].setPlainText(ca_numbers[i]) self.__numbers[i].setFont(font) self.__scene.addItem(self.__letters[i]) self.__scene.addItem(self.__numbers[i]) self.__shipListItem.setPixmap(QPixmap.fromImage(self.__shipListImage)) self.__shipListItem.setTransformationMode( Qt.TransformationMode.SmoothTransformation) self.__scene.addItem(self.__shipListItem) font.setPixelSize(int(self.__originalTileSize * 0.3)) for _, ship in self.__shipList.items(): t = QTransform().rotate(90) ship.shipItem.setPixmap( QPixmap.fromImage(ship.image).transformed(t)) ship.shipItem.setTransformationMode( Qt.TransformationMode.SmoothTransformation) ship.counterItem.setPixmap(QPixmap.fromImage(self.__counterImage)) ship.counterItem.setTransformationMode( Qt.TransformationMode.SmoothTransformation) ship.counterText.setPlainText(str(ship.count)) ship.counterText.setFont(font) self.__scene.addItem(ship.shipItem) self.__scene.addItem(ship.counterItem) self.__scene.addItem(ship.counterText) self.__ghostShip.setTransformationMode( Qt.TransformationMode.SmoothTransformation) self.__ghostShip.setOpacity(0.7) pen = QPen() pen.setWidth(2) pen.setStyle(Qt.PenStyle.DashLine) pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) self.__placer.setPen(pen) def __setCell(self, x, y, cell_type): if cell_type not in self.__cellImages.keys(): raise ValueError( f"Type is {cell_type}. Allowed \"intact\", \"miss\", \"hit\"") cellItem = self.__field[y * 10 + x] cellItem.setData(0, cell_type) pixmap = QPixmap.fromImage(self.__cellImages[cell_type]) cellItem.setPixmap(pixmap) def __runAnimation(self, x, y, animation, looped=False): sprite = SpriteItem() sprite.setData(0, QPoint(x, y)) spritePixmap = QPixmap.fromImage(self.__sprites[animation]) sprite.setSpriteMap(spritePixmap, 60, 60, 5) sprite.setScale(self.__scaleFactor) sprite.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize) sprite.setZValue(10) self.__spriteAnimations.append(sprite) self.__scene.addItem(sprite) def removeAnimation(): self.__scene.removeItem(sprite) self.__spriteAnimations.remove(sprite) sprite.stopAnimation() sprite.startAnimation(100, looped, removeAnimation) def __shootAround(self, ship: Ship): shipCells = [] for i in range(ship.length): shipCells.append(ship.pos + (QPoint(0, i) if ship.vertical else QPoint(i, 0))) cells = [] for cell in shipCells: arounds = [ cell + QPoint(i, j) for i in range(-1, 2) for j in range(-1, 2) ] for candidate in arounds: if 0 <= candidate.x() < 10 and 0 <= candidate.y() < 10: if candidate not in cells: cells.append(candidate) for cell in cells: if cell not in shipCells: self.__setCell(cell.x(), cell.y(), "miss") self.__runAnimation(cell.x(), cell.y(), "splash") log.debug( f"name: {ship.name} | length: {ship.length} | pos: {ship.pos} | vertical: {ship.vertical}" ) def hasHeightForWidth(self): return True def heightForWidth(self, width): return width / self.__ratio def hideShipList(self): self.__ratio = self.RATIO_WITHOUT_SHIPLIST self.__scene.removeItem(self.__shipListItem) for _, ship in self.__shipList.items(): self.__scene.removeItem(ship.shipItem) self.__adjustedToSize = None resize = QResizeEvent(self.size(), self.size()) QApplication.postEvent(self, resize) def getPlacedShips(self): return self.__placedShips def placedShipsCount(self): return len(self.__placedShips) def hideShips(self): for ship in self.__placedShips: self.__scene.removeItem(ship) def removePlacedShips(self): if self.__shipListItem.scene() is None: return for ship in self.__placedShips: shipListItem = ship.data(1) shipListItem.count += 1 shipListItem.counterText.setPlainText(str(shipListItem.count)) self.__scene.removeItem(ship) self.__placedShips.clear() def shuffleShips(self): if self.__shipListItem.scene() is None: return self.removePlacedShips() def findPossibleCells(length, rotation): vertical = rotation.isVertical() if vertical == rotation.isHorizontal(): raise Exception( "Unknown state! Rotation is not horizontal and not vertical." ) # wtf width = 1 if vertical else length height = length if vertical else 1 cells = [] for x in range(10): for y in range(10): if QRect(0, 0, 10, 10) != QRect(0, 0, 10, 10).united( QRect(x, y, width, height)): break if self.__validatePosition(x, y, width, height): cells.append(QPoint(x, y)) return cells shipList = list(self.__shipList.values()) shipList.sort(key=lambda ship: ship.length, reverse=True) for shipItem in shipList: for i in range(shipItem.count): rot = random.choice(list(Rotation)) cells = findPossibleCells(shipItem.length, rot) if not cells: rot = rot.next() cells = findPossibleCells(shipItem.length, rot) if not cells: return cell = random.choice(cells) self.__placeShip(shipItem, cell.x(), cell.y(), rot) def resizeEvent(self, event): size = event.size() if size == self.__adjustedToSize: return self.__adjustedToSize = size nowWidth = size.width() nowHeight = size.height() width = min(nowWidth, nowHeight * self.__ratio) height = min(nowHeight, nowWidth / self.__ratio) h_margin = round((nowWidth - width) / 2) - 2 v_margin = round((nowHeight - height) / 2) - 2 self.setContentsMargins( QMargins(h_margin, v_margin, h_margin, v_margin)) self.__resizeScene() def scanEffect(self, x, y): """ :return: True on success, False otherwise """ if self.__scanEffect: return False def scanFinisedCallback(): self.__scene.removeItem(self.__scanEffect) del self.__scanEffect self.__scanEffect = None target = FadingPixmapItem(self.__targetPixmap) target.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize) target.setScale(self.__scaleFactor) target.setData(0, (x, y)) self.__targets.append(target) self.__scene.addItem(target) self.__scanEffect = FloatingGradientItem( QRectF(self.tileSize, self.tileSize, self.__originalTileSize * 10, self.__originalTileSize * 10), scanFinisedCallback) self.__scanEffect.setScale(self.__scaleFactor) self.__scanEffect.setBackwards(True) self.__scene.addItem(self.__scanEffect) return True def __resizeScene(self): width = self.__ui.graphicsView.width() height = self.__ui.graphicsView.height() self.__scene.setSceneRect(0, 0, width, height) self.tileSize = min(width / 11, height / (11 / self.__ratio)) self.__scaleFactor = self.tileSize / self.__originalTileSize for i, cell in enumerate(self.__field): x = i % 10 y = i // 10 cell.setScale(self.__scaleFactor) cell.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize) for sprite in self.__spriteAnimations: pos = sprite.data(0) sprite.setPos((pos.x() + 1) * self.tileSize, (pos.y() + 1) * self.tileSize) sprite.setScale(self.__scaleFactor) for i in range(10): letter = self.__letters[i] letter.setScale(self.__scaleFactor) offsetX = (self.tileSize - letter.boundingRect().width() * self.__scaleFactor) / 2 offsetY = (self.tileSize - letter.boundingRect().height() * self.__scaleFactor) / 2 letter.setPos((i + 1) * self.tileSize + offsetX, offsetY) number = self.__numbers[i] number.setScale(self.__scaleFactor) offsetX = (self.tileSize - number.boundingRect().width() * self.__scaleFactor) / 2 offsetY = (self.tileSize - number.boundingRect().height() * self.__scaleFactor) / 2 number.setPos(offsetX, (i + 1) * self.tileSize + offsetY) xPos = 0 for _, ship in self.__shipList.items(): xPos += (ship.length - 1) xOffset = xPos * self.tileSize ship.shipItem.setScale(self.__scaleFactor) ship.shipItem.setPos(self.tileSize + xOffset, self.tileSize * (12 + self.EPS)) ship.counterItem.setScale(self.__scaleFactor) ship.counterItem.setPos(self.tileSize + xOffset, self.tileSize * (12.65 + self.EPS)) counterSize = ship.counterItem.boundingRect() textSize = ship.counterText.boundingRect() textXOffset = (counterSize.width() - textSize.width()) * self.__scaleFactor / 2 textYOffset = (counterSize.height() - textSize.height()) * self.__scaleFactor / 2 textX = ship.counterItem.pos().x() + textXOffset textY = ship.counterItem.pos().y() + textYOffset ship.counterText.setScale(self.__scaleFactor) ship.counterText.setPos(textX, textY) for ship in self.__placedShips: mapPos = ship.data(2) ship.setPos((mapPos.x() + 1) * self.tileSize, (mapPos.y() + 1) * self.tileSize) ship.setScale(self.__scaleFactor) for target in self.__targets: x, y = target.data(0) target.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize) target.setScale(self.__scaleFactor) if self.__scanEffect: self.__scanEffect.setPos(self.tileSize, self.tileSize) self.__scanEffect.setScale(self.__scaleFactor) shipListX = self.tileSize shipListY = self.tileSize * (11 + self.EPS) self.__shipListItem.setScale(self.__scaleFactor) self.__shipListItem.setPos(shipListX, shipListY) self.__ghostShip.setScale(self.__scaleFactor) def eventFilter(self, obj, event): if obj is self.__ui.graphicsView.viewport(): if event.type() == QEvent.MouseButtonPress: self.__viewportMousePressEvent(event) elif event.type() == QEvent.MouseButtonRelease: self.__viewportMouseReleaseEvent(event) elif event.type() == QEvent.MouseMove: self.__viewportMouseMoveEvent(event) return super().eventFilter(obj, event) def sceneToMap(self, x, y): x -= self.tileSize y -= self.tileSize x //= self.tileSize y //= self.tileSize return int(x), int(y) def __initGhostShip(self, ship, pos): self.__ghostShip.setPixmap(QPixmap.fromImage(ship.image)) self.__ghostShip.setPos(pos) self.__ghostShip.setRotation(0) width = self.__ghostShip.boundingRect().width() height = self.__ghostShip.boundingRect().height() self.__ghostShip.setOffset(-width / 2, -height / 2) self.__ghostShip.setData(0, Rotation.UP) self.__ghostShip.setData(1, ship) self.__placer.setRect(0, 0, self.tileSize, self.tileSize * ship.length) self.__placer.setZValue(50) self.__scene.addItem(self.__ghostShip) self.__ghostShip.setZValue(100) def __rotateGhostShip(self, rotation=None): rotation = rotation if rotation else self.__ghostShip.data(0).next() self.__ghostShip.setData(0, rotation) self.__ghostShip.setRotation(rotation.value) placerRect = self.__placer.rect() maxSide = max(placerRect.width(), placerRect.height()) minSide = min(placerRect.width(), placerRect.height()) if rotation.isHorizontal(): self.__placer.setRect(0, 0, maxSide, minSide) elif rotation.isVertical(): self.__placer.setRect(0, 0, minSide, maxSide) else: raise Exception( "Unknown state! Rotation is not horizontal and not vertical." ) # wtf self.__validatePlacer() def __ghostShipLongSurface(self): pos = self.__ghostShip.pos() x = pos.x() y = pos.y() rot: Rotation = self.__ghostShip.data(0) length = self.__ghostShip.data(1).length if rot.isHorizontal(): x -= self.tileSize * length / 2 return x, y if rot.isVertical(): y -= self.tileSize * length / 2 return x, y def __validatePosition(self, x, y, width=1, height=1): positionRect = QRectF((x + 1) * self.tileSize, (y + 1) * self.tileSize, self.tileSize * width, self.tileSize * height) isPlacerValid = True for ship in self.__placedShips: shipRect = ship.mapRectToScene(ship.boundingRect()).adjusted( -self.tileSize / 2, -self.tileSize / 2, self.tileSize / 2, self.tileSize / 2) isPlacerValid = not positionRect.intersects(shipRect) if not isPlacerValid: break return isPlacerValid def __validatePlacer(self): sceneX, sceneY = self.__ghostShipLongSurface() x, y = self.sceneToMap(sceneX, sceneY) self.__placer.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize) permittedArea = QRectF(self.__ui.graphicsView.viewport().geometry()) permittedArea.setTopLeft(QPointF(self.tileSize, self.tileSize)) permittedArea.setBottomRight( QPointF(self.tileSize * 12, self.tileSize * 12)) placerSize = self.__placer.boundingRect() placerRect = QRectF(sceneX, sceneY, placerSize.width(), placerSize.height()) isPlacerValid = False # first validation - ship can be placed inside game field if permittedArea.contains(self.__ghostShip.pos( )) and permittedArea == permittedArea.united(placerRect): if self.__placer.scene() == None: self.__scene.addItem(self.__placer) x, y = self.sceneToMap(sceneX, sceneY) self.__placer.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize) isPlacerValid = True else: if self.__placer.scene() == self.__scene: self.__scene.removeItem(self.__placer) # second validation - placer does not intersect with other ships if isPlacerValid: isPlacerValid = self.__validatePosition( x, y, placerSize.width() / self.tileSize, placerSize.height() / self.tileSize) # set color of placer pen = self.__placer.pen() if isPlacerValid: pen.setColor(Qt.GlobalColor.darkGreen) else: pen.setColor(Qt.GlobalColor.red) self.__placer.setPen(pen) self.__placer.setData(0, isPlacerValid) def __placeShip(self, shipListItem, x, y, rotation): sceneX, sceneY = (x + 1) * self.tileSize, (y + 1) * self.tileSize shipListItem.count -= 1 shipListItem.counterText.setPlainText(str(shipListItem.count)) pixmap = QPixmap(shipListItem.image).transformed(QTransform().rotate( (rotation.value))) placedShip = QGraphicsPixmapItem(pixmap) placedShip.setData(0, rotation) placedShip.setData(1, shipListItem) placedShip.setData(2, QPoint(x, y)) # position in map coordinates placedShip.setPos(sceneX, sceneY) placedShip.setTransformationMode( Qt.TransformationMode.SmoothTransformation) placedShip.setScale(self.__scaleFactor) self.__placedShips.append(placedShip) self.__scene.addItem(placedShip) def __placeGhostShip(self): isPlacingPermitted = self.__placer.data(0) if isPlacingPermitted: sceneX = self.__placer.pos().x() + self.tileSize / 2 sceneY = self.__placer.pos().y() + self.tileSize / 2 mapX, mapY = self.sceneToMap(sceneX, sceneY) rotation = self.__ghostShip.data(0) if rotation.isVertical(): vertical = True elif rotation.isHorizontal(): vertical = False else: raise Exception( "Unknown state! Rotation is not horizontal and not vertical." ) # wtf shipListItem = self.__ghostShip.data(1) self.__placeShip(shipListItem, mapX, mapY, rotation) log.debug( f"ship \"{shipListItem.name}\"; " f"position ({mapX}, {mapY}); " f"oriented {'vertically' if vertical else 'horizontally'}") self.shipPlaced.emit( Ship(name=shipListItem.name, length=shipListItem.length, pos=QPoint(mapX, mapY), vertical=vertical)) def __viewportMousePressEvent(self, event): if event.button() == Qt.MouseButton.LeftButton: if self.__shipListItem.scene(): # check press on shiplist shipUnderMouse = None for _, ship in self.__shipList.items(): if ship.shipItem.isUnderMouse(): shipUnderMouse = ship break # check press on field rotation = Rotation.RIGHT if shipUnderMouse == None: for ship in self.__placedShips: if ship.isUnderMouse(): rotation = ship.data(0) shipListItem = ship.data(1) if shipListItem.count == 0: shipListItem.shipItem.setGraphicsEffect(None) shipListItem.count += 1 shipListItem.counterText.setPlainText( str(shipListItem.count)) shipUnderMouse = shipListItem self.__placedShips.remove(ship) self.__scene.removeItem(ship) break # if ship grabbed if shipUnderMouse and shipUnderMouse.count > 0: self.__initGhostShip(shipUnderMouse, event.pos()) self.__rotateGhostShip(rotation) self.__dragShip = True x, y = self.sceneToMap(event.pos().x(), event.pos().y()) if x >= 0 and x < 10 and y >= 0 and y < 10: if self.controller.isBot: return self.controller.emitHit(x, y) if event.button() == Qt.MouseButton.RightButton: if self.__dragShip: self.__rotateGhostShip() def __viewportMouseReleaseEvent(self, event): if event.button() == Qt.MouseButton.LeftButton: if self.__dragShip: self.__scene.removeItem(self.__ghostShip) self.__dragShip = False if self.__placer.scene() != None: self.__scene.removeItem(self.__placer) # self.__placeShip() self.__placeGhostShip() self.__placer.setData(0, False) def __viewportMouseMoveEvent(self, event): if self.__dragShip: self.__ghostShip.setPos(event.pos()) permittedArea = QRectF( self.__ui.graphicsView.viewport().geometry()) permittedArea.setTopLeft(QPointF(self.tileSize, self.tileSize)) # stop dragging ship if mouse out of field # if not permittedArea.contains(event.pos()): # self.__scene.removeItem(self.__ghostShip) # self.__dragShip = False self.__validatePlacer() def __accept(self, x, y, hit_type: CellState): log.debug( f" -- ACCEPTED -- hit on point ({x}, {y}) hit type: {hit_type}") cell = self.__field[y * 10 + x] if cell.data(0) != "intact": return if hit_type in [CellState.HIT, CellState.KILLED]: self.__setCell(x, y, 'hit') self.__runAnimation(x, y, "explosion", looped=True) for target in self.__targets: if (x, y) == target.data(0): self.__scene.removeItem(target) self.__targets.remove(target) break elif hit_type in [CellState.MISS]: self.__setCell(x, y, 'miss') self.__runAnimation(x, y, "splash", looped=False) if hit_type == CellState.KILLED: for ship in self.__placedShips: rotation = ship.data(0) length = ship.data(1).length position = ship.data(2) width = 1 if rotation.isVertical() else length height = length if rotation.isVertical() else 1 rect = QRect(position.x(), position.y(), width, height) if rect.contains(x, y): self.__scene.addItem(ship) def __decline(self, x, y): log.debug(f"declined hit on point ({x}, {y})")
def refresh(self): if not self._mdlPlots or not self._mdlOutline or not self._mdlCharacter: return if not self.isVisible(): return LINE_HEIGHT = 18 SPACING = 3 TEXT_WIDTH = self.sldTxtSize.value() CIRCLE_WIDTH = 10 LEVEL_HEIGHT = 12 s = self.scene s.clear() # Get Max Level (max depth) root = self._mdlOutline.rootItem def maxLevel(item, level=0, max=0): if level > max: max = level for c in item.children(): m = maxLevel(c, level + 1) if m > max: max = m return max MAX_LEVEL = maxLevel(root) # Get the list of tracked items (array of references) trackedItems = [] if self.actPlots.isChecked(): trackedItems += self.plotReferences() if self.actCharacters.isChecked(): trackedItems += self.charactersReferences() ROWS_HEIGHT = len(trackedItems) * (LINE_HEIGHT + SPACING ) fm = QFontMetrics(s.font()) max_name = 0 for ref in trackedItems: name = references.title(ref) max_name = max(fm.width(name), max_name) TITLE_WIDTH = max_name + 2 * SPACING # Add Folders and Texts outline = OutlineRect(0, 0, 0, ROWS_HEIGHT + SPACING + MAX_LEVEL * LEVEL_HEIGHT) s.addItem(outline) outline.setPos(TITLE_WIDTH + SPACING, 0) refCircles = [] # a list of all references, to be added later on the lines # A Function to add a rect with centered elided text def addRectText(x, w, parent, text="", level=0, tooltip=""): deltaH = LEVEL_HEIGHT if level else 0 r = OutlineRect(0, 0, w, parent.rect().height()-deltaH, parent, title=text) r.setPos(x, deltaH) txt = QGraphicsSimpleTextItem(text, r) f = txt.font() f.setPointSize(8) fm = QFontMetricsF(f) elidedText = fm.elidedText(text, Qt.ElideMiddle, w) txt.setFont(f) txt.setText(elidedText) txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) txt.setY(0) return r # A function to returns an item's width, by counting its children def itemWidth(item): if item.isFolder(): r = 0 for c in item.children(): r += itemWidth(c) return r or TEXT_WIDTH else: return TEXT_WIDTH def listItems(item, rect, level=0): delta = 0 for child in item.children(): w = itemWidth(child) if child.isFolder(): parent = addRectText(delta, w, rect, child.title(), level, tooltip=child.title()) parent.setToolTip(references.tooltip(references.textReference(child.ID()))) listItems(child, parent, level + 1) else: rectChild = addRectText(delta, TEXT_WIDTH, rect, "", level, tooltip=child.title()) rectChild.setToolTip(references.tooltip(references.textReference(child.ID()))) # Find tracked references in that scene (or parent folders) for ref in trackedItems: result = [] # Tests if POV scenePOV = False # Will hold true of character is POV of the current text, not containing folder if references.type(ref) == references.CharacterLetter: ID = references.ID(ref) c = child while c: if c.POV() == ID: result.append(c.ID()) if c == child: scenePOV = True c = c.parent() # Search in notes/references c = child while c: result += references.findReferencesTo(ref, c, recursive=False) c = c.parent() if result: ref2 = result[0] # Create a RefCircle with the reference c = RefCircle(TEXT_WIDTH / 2, - CIRCLE_WIDTH / 2, CIRCLE_WIDTH, ID=ref2, important=scenePOV) # Store it, with the position of that item, to display it on the line later on refCircles.append((ref, c, rect.mapToItem(outline, rectChild.pos()))) delta += w listItems(root, outline) OUTLINE_WIDTH = itemWidth(root) # Add Tracked items i = 0 itemsRect = s.addRect(0, 0, 0, 0) itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING) # Set of colors for plots (as long as they don't have their own colors) colors = [ "#D97777", "#AE5F8C", "#D9A377", "#FFC2C2", "#FFDEC2", "#D2A0BC", "#7B0F0F", "#7B400F", "#620C3D", "#AA3939", "#AA6C39", "#882D61", "#4C0000", "#4C2200", "#3D0022", ] for ref in trackedItems: if references.type(ref) == references.CharacterLetter: color = self._mdlCharacter.getCharacterByID(references.ID(ref)).color() else: color = QColor(colors[i % len(colors)]) # Rect r = QGraphicsRectItem(0, 0, TITLE_WIDTH, LINE_HEIGHT, itemsRect) r.setPen(QPen(Qt.NoPen)) r.setBrush(QBrush(color)) r.setPos(0, i * LINE_HEIGHT + i * SPACING) r.setToolTip(references.tooltip(ref)) i += 1 # Text name = references.title(ref) txt = QGraphicsSimpleTextItem(name, r) txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) # Line line = PlotLine(0, 0, OUTLINE_WIDTH + SPACING, 0) line.setPos(TITLE_WIDTH, r.mapToScene(r.rect().center()).y()) s.addItem(line) line.setPen(QPen(color, 5)) line.setToolTip(references.tooltip(ref)) # We add the circles / references to text, on the line for ref2, circle, pos in refCircles: if ref2 == ref: circle.setParentItem(line) circle.setPos(pos.x(), 0) # self.view.fitInView(0, 0, TOTAL_WIDTH, i * LINE_HEIGHT, Qt.KeepAspectRatioByExpanding) # KeepAspectRatio self.view.setSceneRect(0, 0, 0, 0)
def refresh(self): if not self._mdlPlots or not self._mdlOutline or not self._mdlPersos: pass LINE_HEIGHT = 18 SPACING = 3 TEXT_WIDTH = self.sldTxtSize.value() CIRCLE_WIDTH = 10 LEVEL_HEIGHT = 12 s = self.scene s.clear() # Get Max Level (max depth) root = self._mdlOutline.rootItem def maxLevel(item, level=0, max=0): if level > max: max = level for c in item.children(): m = maxLevel(c, level + 1) if m > max: max = m return max MAX_LEVEL = maxLevel(root) # Generate left entries # (As of now, plot only) plotsID = self._mdlPlots.getPlotsByImportance() trackedItems = [] fm = QFontMetrics(s.font()) max_name = 0 for importance in plotsID: for ID in importance: name = self._mdlPlots.getPlotNameByID(ID) ref = references.plotReference(ID, searchable=True) trackedItems.append((ID, ref, name)) max_name = max(fm.width(name), max_name) ROWS_HEIGHT = len(trackedItems) * (LINE_HEIGHT + SPACING ) TITLE_WIDTH = max_name + 2 * SPACING # Add Folders and Texts outline = OutlineRect(0, 0, 0, ROWS_HEIGHT + SPACING + MAX_LEVEL * LEVEL_HEIGHT) s.addItem(outline) outline.setPos(TITLE_WIDTH + SPACING, 0) refCircles = [] # a list of all references, to be added later on the lines # A Function to add a rect with centered elided text def addRectText(x, w, parent, text="", level=0, tooltip=""): deltaH = LEVEL_HEIGHT if level else 0 r = OutlineRect(0, 0, w, parent.rect().height()-deltaH, parent, title=text) r.setPos(x, deltaH) txt = QGraphicsSimpleTextItem(text, r) f = txt.font() f.setPointSize(8) fm = QFontMetricsF(f) elidedText = fm.elidedText(text, Qt.ElideMiddle, w) txt.setFont(f) txt.setText(elidedText) txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) txt.setY(0) return r # A function to returns an item's width, by counting its children def itemWidth(item): if item.isFolder(): r = 0 for c in item.children(): r += itemWidth(c) return r or TEXT_WIDTH else: return TEXT_WIDTH def listItems(item, rect, level=0): delta = 0 for child in item.children(): w = itemWidth(child) if child.isFolder(): parent = addRectText(delta, w, rect, child.title(), level, tooltip=child.title()) parent.setToolTip(references.tooltip(references.textReference(child.ID()))) listItems(child, parent, level + 1) else: rectChild = addRectText(delta, TEXT_WIDTH, rect, "", level, tooltip=child.title()) rectChild.setToolTip(references.tooltip(references.textReference(child.ID()))) # Find tracked references in that scene (or parent folders) for ID, ref, name in trackedItems: result = [] c = child while c: result += references.findReferencesTo(ref, c, recursive=False) c = c.parent() if result: ref2 = result[0] # Create a RefCircle with the reference c = RefCircle(TEXT_WIDTH / 2, - CIRCLE_WIDTH / 2, CIRCLE_WIDTH, ID=ref2) # Store it, with the position of that item, to display it on the line later on refCircles.append((ref, c, rect.mapToItem(outline, rectChild.pos()))) delta += w listItems(root, outline) OUTLINE_WIDTH = itemWidth(root) # Add Plots i = 0 itemsRect = s.addRect(0, 0, 0, 0) itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING) for ID, ref, name in trackedItems: color = randomColor() # Rect r = QGraphicsRectItem(0, 0, TITLE_WIDTH, LINE_HEIGHT, itemsRect) r.setPen(QPen(Qt.NoPen)) r.setBrush(QBrush(color)) r.setPos(0, i * LINE_HEIGHT + i * SPACING) i += 1 # Text txt = QGraphicsSimpleTextItem(name, r) txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) # Line line = PlotLine(0, 0, OUTLINE_WIDTH + SPACING, 0) line.setPos(TITLE_WIDTH, r.mapToScene(r.rect().center()).y()) s.addItem(line) line.setPen(QPen(color, 5)) line.setToolTip(self.tr("Plot: ") + name) # We add the circles / references to text, on the line for ref2, circle, pos in refCircles: if ref2 == ref: circle.setParentItem(line) circle.setPos(pos.x(), 0) # self.view.fitInView(0, 0, TOTAL_WIDTH, i * LINE_HEIGHT, Qt.KeepAspectRatioByExpanding) # KeepAspectRatio self.view.setSceneRect(0, 0, 0, 0)
class SelectAreaDialog(QDialog): finish_selecting_area = pyqtSignal(TrimmingData) def __init__(self): super().__init__() self.ui = Ui_SelectAreaDialog() self.ui.setupUi(self) self.width = 200 self.height = 200 self.h, self.w = None, None self.select_area = None self.original_image_scene = None self.size_flag = True self.select_area_label = None self.select_area_label_proxy = None self.start_position = None self.get_ng_sample_image_path() if self.h <= self.height and self.w <= self.width: self.size_flag = False if self.size_flag: self.show_select_area_at_default_position() else: self.ui.notation_label.setText('この画像サイズは十分小さいため, 画像全体でトレーニングを行います.' '\nこのままトレーニング開始ボタンを押してください.') pass self.ui.ok_button.clicked.connect(self.on_clicked_ok_button) self.ui.cancel_button.clicked.connect(self.on_clicked_cancel_button) def get_ng_sample_image_path(self): test_ng_path = str(Dataset.images_path(Dataset.Category.TEST_NG)) test_ng_images = os.listdir(test_ng_path) test_ng_images = [ img for img in test_ng_images if Path(img).suffix in ['.jpg', '.jpeg', '.png', '.gif', '.bmp'] ] if not test_ng_images: return original_image_path = os.path.join(test_ng_path, test_ng_images[0]) original_image = cv2.imread(original_image_path) h, w, c = original_image.shape self.h, self.w = h, w original_image_shape = QSize(w + 2, h + 10) original_image_item = QGraphicsPixmapItem(QPixmap(original_image_path)) original_image_item.setZValue(0) self.original_image_scene = QGraphicsScene() self.original_image_scene.addItem(original_image_item) self.ui.original_image_view.setScene(self.original_image_scene) self.ui.original_image_view.setBaseSize(original_image_shape) self.ui.original_image_view.setMaximumSize(original_image_shape) self.resize(self.w + 32, self.h + 72) def show_select_area_at_default_position(self): trimming_data = Project.latest_trimming_data() if trimming_data.position: self.start_position = QPoint(trimming_data.position[0], trimming_data.position[1]) rect = QRectF(trimming_data.position[0], trimming_data.position[1], self.width, self.height) else: self.start_position = QPoint((self.w - self.width) // 2, (self.h - self.height) // 2) rect = QRectF((self.w - self.width) // 2, (self.h - self.height) // 2, self.width, self.height) self.select_area = QGraphicsRectItem(rect) self.select_area.setZValue(1) pen = QPen(QColor('#ffa00e')) pen.setWidth(4) pen.setJoinStyle(Qt.RoundJoin) self.select_area.setPen(pen) self.select_area.setFlag(QGraphicsItem.ItemIsMovable, True) self.original_image_scene.addItem(self.select_area) self.select_area_label_proxy = QGraphicsProxyWidget(self.select_area) self.select_area_label = SelectAreaLabel() self.select_area_label.set_label() self.select_area_label_proxy.setWidget(self.select_area_label) self.select_area_label_proxy.setPos( self.select_area.boundingRect().left() + 2, self.select_area.boundingRect().bottom() - self.select_area_label.height() - 2) def on_clicked_ok_button(self): if not self.size_flag: trimming_data = TrimmingData(position=(0, 0), size=(self.w, self.h), needs_trimming=False) self.finish_selecting_area.emit(trimming_data) self.close() else: rel_position = self.select_area.pos() position = (self.start_position.x() + rel_position.x(), self.start_position.y() + rel_position.y()) if position[0] < 0 or position[ 0] > self.w - self.width - 1 or position[ 1] < 0 or position[1] > self.h - self.height - 1: print('Error: Please set area contained in the image.') self.ui.notation_label.setText('エラー: 切り取る領域は画像内に収まるようにしてください.') else: trimming_data = TrimmingData(position=position, size=(self.width, self.height), needs_trimming=True) self.finish_selecting_area.emit(trimming_data) self.close() def on_clicked_cancel_button(self): self.close() def closeEvent(self, QCloseEvent): self.close()
class SvgView(QGraphicsView): """ SVG display taken from the PyQt5 examples (see license.txt) """ Native, OpenGL, Image = range(3) def __init__(self, parent=None): super(SvgView, self).__init__(parent) self.renderer = SvgView.Native self.svgItem = None self.backgroundItem = None self.outlineItem = None self.image = QImage() self.setScene(QGraphicsScene(self)) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setDragMode(QGraphicsView.ScrollHandDrag) self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) self.TRANSLUCENT_WHITE = QColor(255, 255, 255, 180) # Prepare background check-board pattern. tilePixmap = QPixmap(64, 64) tilePixmap.fill(Qt.white) tilePainter = QPainter(tilePixmap) color = QColor(220, 220, 220) tilePainter.fillRect(0, 0, 32, 32, color) tilePainter.fillRect(32, 32, 32, 32, color) tilePainter.end() self.setBackgroundBrush(QBrush(tilePixmap)) self.num_x = 1000 self.num_y = 1500 # zoom tracker self.zoom = Zoom() # position tracker self.x = self.width() / 2 self.y = self.height() / 2 self.r = 10 # scales when zooming #self.r_mult = 1 def openFile(self, svg_file): if not svg_file.exists(): return s = self.scene() if self.backgroundItem: drawBackground = self.backgroundItem.isVisible() else: drawBackground = False if self.outlineItem: drawOutline = self.outlineItem.isVisible() else: drawOutline = True s.clear() self.resetTransform() self.svgItem = MapItem(svg_file.fileName()) self.svgItem.setFlags(QGraphicsItem.ItemClipsToShape) self.svgItem.setCacheMode(QGraphicsItem.NoCache) self.svgItem.setZValue(0) self.backgroundItem = QGraphicsRectItem(self.svgItem.boundingRect()) self.backgroundItem.setBrush(Qt.white) self.backgroundItem.setPen(QPen(Qt.NoPen)) self.backgroundItem.setVisible(drawBackground) self.backgroundItem.setZValue(-1) self.outlineItem = QGraphicsRectItem(self.svgItem.boundingRect()) outline = QPen(Qt.black, 2, Qt.DashLine) outline.setCosmetic(True) self.outlineItem.setPen(outline) self.outlineItem.setBrush(QBrush(Qt.NoBrush)) self.outlineItem.setVisible(drawOutline) self.outlineItem.setZValue(1) s.addItem(self.backgroundItem) s.addItem(self.svgItem) s.addItem(self.outlineItem) self.svgItem.setRadius(self.r) self.svgItem.makeGrid(self.num_x, self.num_y, self.svgItem.boundingRect().width(), self.svgItem.boundingRect().height()) self.svgItem.resetGrid() s.setSceneRect(self.outlineItem.boundingRect().adjusted(-10, -10, 10, 10)) self.cursor_circle = fillCircle(self.outlineItem.scene(), self.x, self.y, self.r, self.TRANSLUCENT_WHITE) def setHighQualityAntialiasing(self, highQualityAntialiasing): if QGLFormat.hasOpenGL(): self.setRenderHint(QPainter.HighQualityAntialiasing, highQualityAntialiasing) def removeItem(self, item): self.scene().removeItem(item) def setRadius(self, r): self.r = r self.svgItem.setRadius(r) def setViewBackground(self, enable): if self.backgroundItem: self.backgroundItem.setVisible(enable) def setViewOutline(self, enable): if self.outlineItem: self.outlineItem.setVisible(enable) def paintEvent(self, event): super(SvgView, self).paintEvent(event) p = QPainter(self.viewport()) def updateCursorCircle(self): self.removeItem(self.cursor_circle) transformedPos = self.mapToScene(self.pos) x = transformedPos.x() y = transformedPos.y() self.cursor_circle = fillCircle(self.outlineItem.scene(), x, y, self.r, self.TRANSLUCENT_WHITE) def mouseMoveEvent(self, event): super(SvgView, self).mouseMoveEvent(event) self.pos = event.pos() #QCursor.pos() self.updateCursorCircle() def wheelEvent(self, event): self.zoom.change_scroll(event.angleDelta().y()) factor = pow(1.1, self.zoom.zoom_delta()) self.scale(factor, factor) # TODO: test floating point problems with this implementation # (repeatedly zoom in and out and see if it becomes inaccurate) #self.r_mult = self.r_mult * factor self.updateCursorCircle() event.accept() def keyPressEvent(self, event): super(SvgView, self).keyPressEvent(event) if event.key() == Qt.Key_Plus: self.setRadius(self.r * 1.2) self.updateCursorCircle() elif event.key() == Qt.Key_Minus: self.setRadius(self.r / 1.2) self.updateCursorCircle()
class Board(QGraphicsView): def __init__(self, parent, config, currentMap, currentStage, bridge, gameTypeData, playerData): QGraphicsView.__init__(self, parent) self.config = config self.currentMap = currentMap self.currentStage = currentStage self.bridge = bridge self.gameTypeData = gameTypeData self.isOnline = gameTypeData.isOnline self.numOfPlayers = gameTypeData.numOfPlayers self.playerData = playerData self.isGameOver = False # set up player and enemy details # incremented when generating players (used on local) self.playersAlive = 0 self.playerColors = ["yellow", "green"] self.playerLevels = self.config.playerLevels self.playerWrappers = {} self.enemyColor = "gray" self.enemiesEtds = {} self.enemies = {} self.enemySpawnRegionWidth = self.config.enemySpawnRegionWidth self.enemySpawnRegionHeight = self.config.enemySpawnRegionHeight self.enemySpawnInterval = 3000 self.maxEnemyCnt = 20 self.currentEnemyCnt = 0 self.maxEnemiesCurrentlyAlive = 6 self.enemiesCurrentlyAlive = 0 # ENEMY MOVEMENT TIMERS self.enemyMovementTimers = {} self.slowEnemyMovementTimer = QTimer() self.slowEnemyMovementTimer.setTimerType(Qt.PreciseTimer) self.slowEnemyMovementTimer.setInterval( self.config.enemyMovementSpeedMap["slow"]) self.slowEnemyMovementTimer.start() self.enemyMovementTimers["slow"] = self.slowEnemyMovementTimer self.normalEnemyMovementTimer = QTimer() self.normalEnemyMovementTimer.setTimerType(Qt.PreciseTimer) self.normalEnemyMovementTimer.setInterval( self.config.enemyMovementSpeedMap["normal"]) self.normalEnemyMovementTimer.start() self.enemyMovementTimers["normal"] = self.normalEnemyMovementTimer self.fastEnemyMovementTimer = QTimer() self.fastEnemyMovementTimer.setTimerType(Qt.PreciseTimer) self.fastEnemyMovementTimer.setInterval( self.config.enemyMovementSpeedMap["fast"]) self.fastEnemyMovementTimer.start() self.enemyMovementTimers["fast"] = self.fastEnemyMovementTimer # ENEMY SHOOTING TIMER self.enemyShootingTimer = QTimer() self.enemyShootingTimer.setTimerType(Qt.PreciseTimer) self.enemyShootingTimer.setInterval(self.config.enemyShootInterval) self.enemyShootingTimer.start() # SET UP RANDOM ENEMY SPAWNING TIMER self.enemySpawnTimer = QTimer() self.enemySpawnTimer.setTimerType(Qt.PreciseTimer) self.enemySpawnTimer.timeout.connect(self.generateEnemy) self.enemySpawnTimer.setInterval(self.enemySpawnInterval) self.enemySpawnTimer.start() # BULLET REFRESH RATE # i've chose not to make different timers for different speeds just to save # on resources and computational power # difference from tank movement is that tanks constantly move by 1px # and bullets don't need to so we change the number of pixel as the movement pace for bullets self.bulletTimer = QTimer() self.bulletTimer.setTimerType(Qt.PreciseTimer) self.bulletTimer.setInterval(10) self.bulletTimer.start() # movement animation timer self.animationTimer = QTimer() self.animationTimer.setTimerType(Qt.PreciseTimer) self.animationTimer.setInterval(100) self.animationTimer.start() # each player and enemy will have this emitter passed to them # and will give it to each bullet so the bullet can signal when an enemy has been killed self.killEmitter = KillEmitter() self.killEmitter.emitKillSignal.connect(self.killEmitterHandler) # player dead emitter # each player has one and when the player has no more lives, it emits this signal self.playerDeadEmitter = PlayerDeadEmitter() self.playerDeadEmitter.playerDeadSignal.connect( self.playerDeadEmitterHandler) # explosion sound whenever an enemy is destroyed self.enemyExplosionSound = oalOpen(self.config.sounds["explosion"]) # explosion sound whenever a player is destroyed self.playerExplosionSound = oalOpen( self.config.sounds["playerExplosion"]) # # # moving sound # self.movementSound = oalOpen(self.config.sounds["tankMoving"]) # self.movementSound.set_looping(True) # self.movementSound.set_gain(30.0) # # not moving sound # self.nonMovementSound = oalOpen(self.config.sounds["tankNotMoving"]) # self.nonMovementSound.set_looping(True) # self.nonMovementSound.set_gain(30.0) # initialize board ui self.__init_ui__() # 34 is the max width of the enemy tank self.randomEnemyPositions = [ self.field.x(), self.field.x() + (self.field.boundingRect().width() - 1) / 2, # '-34' doesn't let enemies spawn outside the field self.field.x() + self.field.boundingRect().width() - 34 ] # GAME OVER setup self.gameOverEmitter = GameOverEmitter() self.gameOverEmitter.gameOverSignal.connect(self.gameOverHandler) # game over animation # we need to keep the gameOverAnimation alive so it can animate self.gameOver = GameOver(self.config.gameOverTexture) self.gameOver.setZValue(3) self.gameOver.setPos(QPointF(150, self.fieldBottom + 50)) self.gameOverTimer = QTimer() self.gameOverTimer.setTimerType(Qt.PreciseTimer) self.gameOverTimer.setInterval(15) self.gameOverTimer.timeout.connect(self.animateGameOver) self.gameOverSound = oalOpen(self.config.sounds["gameOver"]) # deus ex spawner and its possible locations self.deusExLocations = [ QPointF(self.field.x(), self.field.y()), QPointF(self.field.x() + 160, self.field.y()), QPointF(self.field.x() + 240, self.field.y()), QPointF(self.field.x() + 320, self.field.y()), QPointF(self.field.x(), self.field.y() + 160), QPointF(self.field.x() + 160, self.field.y() + 160), QPointF(self.field.x() + 240, self.field.y() + 160), QPointF(self.field.x() + 320, self.field.y() + 160), QPointF(self.field.x(), self.field.y() + 240), QPointF(self.field.x() + 160, self.field.y() + 240), QPointF(self.field.x() + 240, self.field.y() + 240), QPointF(self.field.x() + 320, self.field.y() + 240), QPointF(self.field.x(), self.field.y() + 320), QPointF(self.field.x() + 160, self.field.y() + 320), QPointF(self.field.x() + 240, self.field.y() + 320), QPointF(self.field.x() + 320, self.field.y() + 320) ] # deusExActivities self.positiveDeusExActivities = [ self.destroyCurrentlyAliveEnemies, self.playerShield, self.playerLevelUp, self.playerLifeUp, self.stopTheTime, self.upgradeBase ] self.negativeDeusExActivities = [ self.playerLevelDown, self.playerLifeDown, self.playerLosePoints, self.playerCantMove, self.removeBaseShield ] self.deusExSpawner = DeusExSpawner(self.scene, self.config, 15000, self.deusExActivate, self.deusExLocations) self.generateEtd() self.movementSoundHandler = MovementSoundHandler(self.config) self.movementSoundHandler.activate() self.generatePlayers() # UI INITIALIZATION def __init_ui__(self): # set up the scene self.scene = QGraphicsScene() # these 10 subtracted pixels are for the margin self.scene.setSceneRect(0, 0, self.config.mainWindowSize["width"] - 10, self.config.mainWindowSize["height"] - 10) self.scene.setBackgroundBrush(Qt.darkGray) # set up the view self.setScene(self.scene) self.setFixedSize(self.config.mainWindowSize["width"], self.config.mainWindowSize["height"]) # optimization self.setOptimizationFlag(QGraphicsView.DontAdjustForAntialiasing) self.setOptimizationFlag(QGraphicsView.DontSavePainterState) self.setCacheMode(QGraphicsView.CacheBackground) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setInteractive(False) self.setViewport(QGLWidget(QGLFormat())) self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) # initialize the map self.generateMap() # HUD # stage hud self.hudCurrentStage = HudCurrentStage(self.config, self.currentStage) self.hudCurrentStage.setX(self.field.x() + self.field.boundingRect().width() + 20) self.hudCurrentStage.setY(self.field.y() + self.field.boundingRect().height() - 100) self.scene.addItem(self.hudCurrentStage) # player lives hud self.hudPlayersLives = {} if self.isOnline: pass else: for i in range(self.numOfPlayers): if i == 0: if self.playerData.firstPlayerDetails.isAlive: playerLives = HudPlayerLives( i, self.config, self.playerData.firstPlayerDetails.lives) playerLives.setX(self.field.x() + self.field.boundingRect().width() + 20) playerLives.setY(self.field.y() + self.field.boundingRect().height() - 220 + i * 60) self.scene.addItem(playerLives) self.hudPlayersLives[self.playerData.firstPlayerDetails .id] = playerLives elif i == 1: if self.playerData.secondPlayerDetails.isAlive: playerLives = HudPlayerLives( i, self.config, self.playerData.secondPlayerDetails.lives) playerLives.setX(self.field.x() + self.field.boundingRect().width() + 20) playerLives.setY(self.field.y() + self.field.boundingRect().height() - 220 + i * 60) self.scene.addItem(playerLives) self.hudPlayersLives[ self.playerData.secondPlayerDetails. id] = playerLives # enemies left hud self.enemyHud = HudEnemyContainer(self.config) self.enemyHud.setX(self.field.x() + self.field.boundingRect().width() + 20) self.enemyHud.setY(self.field.y() + 40) self.scene.addItem(self.enemyHud) def generateMap(self): # save block textures self.blockTextures = self.config.blockTextures # set up the field self.field = QGraphicsRectItem(0, 0, 520, 520) self.field.setZValue(-1) self.field.setBrush(Qt.black) self.field.setX(40) self.field.setY(20) self.scene.addItem(self.field) # save these for later use self.fieldCenterX = self.field.x() + ( self.field.boundingRect().width() - 1) / 2 self.fieldBottom = self.field.y() + self.field.boundingRect().height( ) - 1 self.baseBlocks = [] # set up the map for b in self.config.maps[f"map{self.currentMap}"]["blueprint"]: blockX = b["xCoord"] blockY = b["yCoord"] blockType = b["type"] blockIsBase = b["isBaseBlock"] block = Block(blockX, blockY, blockType, blockIsBase, self.blockTextures) # hold reference to base blocks if block.isBaseBlock: self.baseBlocks.append(block) # setting z value to be higher than others so the tanks would appear under the bush if blockType == BlockType.bush: block.setZValue(2) # setting z value lower than others so the tanks would appear above the ice elif blockType == BlockType.ice: block.setZValue(-1) self.scene.addItem(block) # add the base self.base = Base(self.config.baseTextures["aliveBase"], self.config.baseTextures["deadBase"]) self.base.setX(self.fieldCenterX - self.base.aliveImage.width() / 2) self.base.setY(self.fieldBottom - self.base.aliveImage.height()) self.scene.addItem(self.base) def generatePlayers(self): if self.isOnline: pass else: for i in range(self.numOfPlayers): if i == 0: firingKey = Qt.Key_Space movementKeys = { "Up": Qt.Key_Up, "Down": Qt.Key_Down, "Left": Qt.Key_Left, "Right": Qt.Key_Right } movementNotifier = MovementNotifier( self.config.playerMovementSpeed) if self.playerData.firstPlayerDetails.isAlive: movementNotifier.movementSignal.connect( self.updatePosition) firingNotifier = FiringNotifier(50) if self.playerData.firstPlayerDetails.isAlive: firingNotifier.firingSignal.connect(self.fireCanon) playerDetails = self.playerData.firstPlayerDetails playerWrapper = PlayerWrapper( playerDetails, self.config, self.playerColors[i], firingKey, movementKeys, firingNotifier, movementNotifier, self.playerLevels, self.field, self.killEmitter, self.bulletTimer, Enemy, self.animationTimer, self.playerDeadEmitter, self.gameOverEmitter) if self.playerData.firstPlayerDetails.isAlive: playerWrapper.player.canShootSignal.connect( self.allowFiring) startingPos = QPointF( self.fieldCenterX - self.base.boundingRect().width() / 2 - self.base.boundingRect().width() * 2, self.fieldBottom - playerWrapper.player.boundingRect().height() - 5) playerWrapper.player.startingPos = startingPos self.scene.addItem(playerWrapper.player) self.playersAlive += 1 playerWrapper.player.setPos(startingPos) playerWrapper.movementNotifier.movementSignal.connect( self.movementSoundHandler.playMovementSound) self.playerWrappers[playerDetails.id] = playerWrapper elif i == 1: firingKey = Qt.Key_J movementKeys = { "Up": Qt.Key_W, "Down": Qt.Key_S, "Left": Qt.Key_A, "Right": Qt.Key_D } movementNotifier = MovementNotifier( self.config.playerMovementSpeed) if self.playerData.secondPlayerDetails.isAlive: movementNotifier.movementSignal.connect( self.updatePosition) firingNotifier = FiringNotifier(50) if self.playerData.secondPlayerDetails.isAlive: firingNotifier.firingSignal.connect(self.fireCanon) playerDetails = self.playerData.secondPlayerDetails playerWrapper = PlayerWrapper( playerDetails, self.config, self.playerColors[i], firingKey, movementKeys, firingNotifier, movementNotifier, self.playerLevels, self.field, self.killEmitter, self.bulletTimer, Enemy, self.animationTimer, self.playerDeadEmitter, self.gameOverEmitter) if self.playerData.secondPlayerDetails.isAlive: playerWrapper.player.canShootSignal.connect( self.allowFiring) startingPos = QPointF( self.fieldCenterX + self.base.boundingRect().width() / 2 + self.base.boundingRect().width(), self.fieldBottom - playerWrapper.player.boundingRect().height() - 5) playerWrapper.player.startingPos = startingPos self.scene.addItem(playerWrapper.player) self.playersAlive += 1 playerWrapper.player.setPos(startingPos) playerWrapper.movementNotifier.movementSignal.connect( self.movementSoundHandler.playMovementSound) self.playerWrappers[playerDetails.id] = playerWrapper def generateEtd(self): # generate enemy details etdFactory = EnemyTankDetailsFactory( self.config.enemyTypes, self.config.enemyTypeIds, self.config.maps[f"map{self.currentMap}"]["enemies"], self.config.bulletSpeedMap) if self.enemiesEtds: self.enemiesEtds.clear() self.enemiesEtds = etdFactory.generateEnemiesDetails() def generateEnemy(self): if self.currentEnemyCnt < self.maxEnemyCnt: if self.enemiesCurrentlyAlive < self.maxEnemiesCurrentlyAlive: # set enemy pos and check if it can be spawned posX1 = random.choice(self.randomEnemyPositions) posY1 = self.field.y() posX2 = posX1 + self.enemySpawnRegionWidth posY2 = posY1 + self.enemySpawnRegionHeight middleX = posX1 + self.enemySpawnRegionWidth / 2 middleY = posY1 + self.enemySpawnRegionHeight / 2 item = self.scene.itemAt(posX1, posY1, self.transform()) if type(item) != QGraphicsRectItem and item is not None: return else: item = self.scene.itemAt(posX1, posY2, self.transform()) if type(item) != QGraphicsRectItem and item is not None: return else: item = self.scene.itemAt(posX2, posY1, self.transform()) if type(item ) != QGraphicsRectItem and item is not None: return else: item = self.scene.itemAt(posX2, posY2, self.transform()) if type( item ) != QGraphicsRectItem and item is not None: return else: item = self.scene.itemAt( middleX, middleY, self.transform()) if type( item ) != QGraphicsRectItem and item is not None: return else: pass enemyEtd = self.enemiesEtds[self.currentEnemyCnt] # set if tank is flashing or not isFlashing = False if self.currentEnemyCnt == 3 or self.currentEnemyCnt == 11 or self.currentEnemyCnt == 17: isFlashing = True enemy = Enemy(self.currentEnemyCnt, enemyEtd, isFlashing, self.enemyColor, self.field, self.enemyMovementTimers[enemyEtd.movementSpeed], self.enemyShootingTimer, self.animationTimer, self.bulletTimer, Player, self.killEmitter, self.gameOverEmitter) self.scene.removeItem(self.enemyHud.removeEnemy()) self.scene.addItem(enemy) enemy.setPos(posX1, posY1) self.enemies[enemy.id] = enemy self.currentEnemyCnt += 1 self.enemiesCurrentlyAlive += 1 def keyPressEvent(self, event): playedSoundCnt = 0 if not self.isGameOver: key = event.key() playerWrapper: PlayerWrapper for playerWrapper in self.playerWrappers.values(): if key == playerWrapper.firingKey: playerWrapper.firingNotifier.add_key(key) elif key in playerWrapper.movementKeys.values(): playerWrapper.movementNotifier.add_key(key) def keyReleaseEvent(self, event): playedSoundCnt = 0 if not self.isGameOver: key = event.key() playerWrapper: PlayerWrapper for playerWrapper in self.playerWrappers.values(): if key == playerWrapper.firingKey: playerWrapper.firingNotifier.remove_key(key) elif key in playerWrapper.movementKeys.values(): playerWrapper.movementNotifier.remove_key(key) def updatePosition(self, key): playerWrapper: PlayerWrapper for playerWrapper in self.playerWrappers.values(): if key in playerWrapper.movementKeys.values(): playerWrapper.player.updatePosition(key) def allowFiring(self, canShootSignalData): self.playerWrappers[ canShootSignalData. playerId].firingNotifier.canEmit = canShootSignalData.canEmit def fireCanon(self, key): playerWrapper: PlayerWrapper for playerWrapper in self.playerWrappers.values(): if key == playerWrapper.firingKey: playerWrapper.player.shoot(key) def killEmitterHandler(self, killEmitterData): if killEmitterData.targetType is Enemy: # add points, check if the enemy is flashing and if so, spawn a positive DeusEx enemy = self.enemies[killEmitterData.targetId] playerWrapper = self.playerWrappers[killEmitterData.shooterId] playerWrapper.player.points += enemy.tankDetails.points playerWrapper.separateTankDetails.details[ enemy.tankDetails.stringTankType]["count"] += 1 # if the enemy is flashing then give the players a positive DeusEx if enemy.isFlashing: self.deusExSpawner.spawn(isPositive=True) # remove the tank, its details and delete it for good del self.enemiesEtds[killEmitterData.targetId] self.scene.removeItem(self.enemies[killEmitterData.targetId]) sip.delete(self.enemies[killEmitterData.targetId]) del self.enemies[killEmitterData.targetId] self.enemiesCurrentlyAlive -= 1 self.enemyExplosionSound.play() # if there are no more enemy tank details that means that the stage is over if not self.enemiesEtds: self.stageEndInitiate() elif killEmitterData.targetType is Player: player = self.playerWrappers[killEmitterData.targetId].player player.resetPlayer() # if lives are less than 0, that means the player is dead if player.lives >= 0: self.hudPlayersLives[player.id].updateLives(player.lives) self.playerExplosionSound.play() def playerDeadEmitterHandler(self, playerId): playerWrapper = self.playerWrappers[playerId] # stop all player notifiers playerWrapper.firingNotifier.firingSignal.disconnect() playerWrapper.movementNotifier.movementSignal.disconnect() playerWrapper.firingNotifier.thread.terminate() playerWrapper.movementNotifier.thread.terminate() playerWrapper.player.isAlive = False # remove player from scene, but still keep it's data self.scene.removeItem(playerWrapper.player) # delete the reference to a player because in gameover handler # we will go over the rest of players and remove them from the scene # so now we set the player reference to None because of the check in gameover handler # decrease the number of players alive, if now is 0, all players are dead and the game is over self.playersAlive -= 1 if self.playersAlive == 0: self.gameOverHandler() def stageEndInitiate(self): self.stageEndTimer = QTimer() self.stageEndTimer.setTimerType(Qt.PreciseTimer) self.stageEndTimer.setInterval(3000) self.stageEndTimer.timeout.connect(self.stageEnd) self.stageEndTimer.start() def stageEnd(self): self.stageEndTimer.stop() # disable all timers until next stage # self.nonMovementSound.stop() # self.movementSound.stop() self.movementSoundHandler.deactivate() self.animationTimer.stop() self.bulletTimer.stop() for timer in self.enemyMovementTimers.values(): timer.stop() self.enemySpawnTimer.stop() self.enemyShootingTimer.stop() self.deusExSpawner.spawnTimer.stop() # stop any deus ex activity timer that is active for pw in self.playerWrappers.values(): shieldTimer = getattr(self, f"playerShieldTimer{pw.player.id}", None) if shieldTimer is not None: shieldTimer.stop() del shieldTimer cantMoveTimer = getattr(self, f"playerCantMoveTimer{pw.player.id}", None) if cantMoveTimer is not None: cantMoveTimer.stop() del cantMoveTimer upgradeBaseTimer = getattr(self, f"upgradeBaseTimer", None) if upgradeBaseTimer is not None: upgradeBaseTimer.stop() stopTheTimeTimer = getattr(self, f"stopTheTimeTimer", None) if stopTheTimeTimer is not None: stopTheTimeTimer.stop() removeBaseShieldTimer = getattr(self, f"removeBaseShieldTimer", None) if removeBaseShieldTimer is not None: removeBaseShieldTimer.stop() # save data to be sent back to main window and disconnect and clear the players firstPlayerDetails = None firstPlayerTankDetails = None secondPlayerDetails = None secondPlayerTankDetails = None if self.numOfPlayers == 1: firstPlayerDetails = self.playerWrappers[ self.playerData.firstPlayerDetails.id].getPlayerDetails() firstPlayerTankDetails = self.playerWrappers[ self.playerData.firstPlayerDetails.id].separateTankDetails else: firstPlayerDetails = self.playerWrappers[ self.playerData.firstPlayerDetails.id].getPlayerDetails() firstPlayerTankDetails = self.playerWrappers[ self.playerData.firstPlayerDetails.id].separateTankDetails secondPlayerDetails = self.playerWrappers[ self.playerData.secondPlayerDetails.id].getPlayerDetails() secondPlayerTankDetails = self.playerWrappers[ self.playerData.secondPlayerDetails.id].separateTankDetails playerWrapper: PlayerWrapper for playerWrapper in self.playerWrappers.values(): # check if player is dead, if not, disconnect from all notifiers # if he's dead, he already disconnected, and is already removed from the scene if playerWrapper.player is not None and playerWrapper.player.isAlive: playerWrapper.firingNotifier.firingSignal.disconnect() playerWrapper.movementNotifier.movementSignal.disconnect() playerWrapper.firingNotifier.thread.terminate() playerWrapper.movementNotifier.thread.terminate() self.scene.removeItem(playerWrapper.player) sip.delete(playerWrapper.firingNotifier) sip.delete(playerWrapper.movementNotifier) sip.delete(playerWrapper.player) del playerWrapper.firingNotifier del playerWrapper.movementNotifier del playerWrapper.player self.playerWrappers.clear() self.scene.clear() # send data if self.isOnline: pass else: if self.numOfPlayers == 1: data = LocalGameData(firstPlayerDetails, firstPlayerTankDetails) self.bridge.localGameStageEndSignal.emit(data) else: data = LocalGameData(firstPlayerDetails, firstPlayerTankDetails, secondPlayerDetails, secondPlayerTankDetails) self.bridge.localGameStageEndSignal.emit(data) def gameOverHandler(self): if self.base.isAlive: # self.nonMovementSound.stop() # self.movementSound.stop() self.movementSoundHandler.deactivate() self.deusExSpawner.spawnTimer.stop() self.isGameOver = True self.base.destroyBase() playerWrapper: PlayerWrapper for playerWrapper in self.playerWrappers.values(): # check if player is dead, if not, disconnect from all notifiers # if he's dead, he already disconnected, and is removed from the scene if playerWrapper.player is not None and playerWrapper.player.isAlive: playerWrapper.firingNotifier.firingSignal.disconnect() playerWrapper.movementNotifier.movementSignal.disconnect() playerWrapper.firingNotifier.thread.terminate() playerWrapper.movementNotifier.thread.terminate() self.scene.removeItem(playerWrapper.player) # self.nonMovementSound.destroy() # self.movementSound.destroy() # del self.nonMovementSound # del self.movementSound # ANIMATE GAME OVER self.scene.addItem(self.gameOver) self.gameOverTimer.start() self.gameOverSound.play() # disconnect from game over signal so there won't be more animations self.gameOverEmitter.gameOverSignal.disconnect() def animateGameOver(self): self.gameOver.setY(self.gameOver.y() - 2) if int(self.gameOver.y()) == 150: self.gameOverTimer.stop() self.gameOverHandlerSendData() def gameOverHandlerSendData(self): self.animationTimer.stop() self.bulletTimer.stop() for timer in self.enemyMovementTimers.values(): timer.stop() self.enemySpawnTimer.stop() self.enemyShootingTimer.stop() self.deusExSpawner.spawnTimer.stop() # stop any deus ex activity timer that is active for pw in self.playerWrappers.values(): shieldTimer = getattr(self, f"playerShieldTimer{pw.player.id}", None) if shieldTimer is not None: shieldTimer.stop() del shieldTimer cantMoveTimer = getattr(self, f"playerCantMoveTimer{pw.player.id}", None) if cantMoveTimer is not None: cantMoveTimer.stop() del cantMoveTimer upgradeBaseTimer = getattr(self, f"upgradeBaseTimer", None) if upgradeBaseTimer is not None: upgradeBaseTimer.stop() stopTheTimeTimer = getattr(self, f"stopTheTimeTimer", None) if stopTheTimeTimer is not None: stopTheTimeTimer.stop() removeBaseShieldTimer = getattr(self, f"removeBaseShieldTimer", None) if removeBaseShieldTimer is not None: removeBaseShieldTimer.stop() # save data firstPlayerDetails = None firstPlayerTankDetails = None secondPlayerDetails = None secondPlayerTankDetails = None if self.numOfPlayers == 1: firstPlayerDetails = self.playerWrappers[ self.playerData.firstPlayerDetails.id].getPlayerDetails() firstPlayerTankDetails = self.playerWrappers[ self.playerData.firstPlayerDetails.id].separateTankDetails else: firstPlayerDetails = self.playerWrappers[ self.playerData.firstPlayerDetails.id].getPlayerDetails() firstPlayerTankDetails = self.playerWrappers[ self.playerData.firstPlayerDetails.id].separateTankDetails secondPlayerDetails = self.playerWrappers[ self.playerData.secondPlayerDetails.id].getPlayerDetails() secondPlayerTankDetails = self.playerWrappers[ self.playerData.secondPlayerDetails.id].separateTankDetails if self.isOnline: pass else: if self.numOfPlayers == 1: data = LocalGameData(firstPlayerDetails, firstPlayerTankDetails) self.bridge.localGameOverSignal.emit(data) else: data = LocalGameData(firstPlayerDetails, firstPlayerTankDetails, secondPlayerDetails, secondPlayerTankDetails) self.bridge.localGameOverSignal.emit(data) playerWrapper: PlayerWrapper for playerWrapper in self.playerWrappers.values(): # check if player is dead, if not, disconnect from all notifiers # if he's dead, he already disconnected, and is removed from the scene if playerWrapper.player is not None and playerWrapper.player.isAlive: sip.delete(playerWrapper.firingNotifier) sip.delete(playerWrapper.movementNotifier) sip.delete(playerWrapper.player) del playerWrapper.firingNotifier del playerWrapper.movementNotifier del playerWrapper.player self.playerWrappers.clear() self.scene.clear() def deusExActivate(self, deusExSignalData): deusExChoice = None if deusExSignalData.deusExType is DeusExTypes.POSITIVE: deusExChoice = random.choice(self.positiveDeusExActivities) else: deusExChoice = random.choice(self.negativeDeusExActivities) for player in deusExSignalData.markedPlayers: pw = self.playerWrappers[player.id] deusExChoice(pw) self.deusExSpawner.spawnTimer.start() # DEUS EX ACTIVITIES # POSITIVE def destroyCurrentlyAliveEnemies(self, pw=None): if self.enemies: for id, enemy in self.enemies.items(): self.scene.removeItem(enemy) del self.enemiesEtds[id] # although this looks like it would produce an error because of deleting elements from currently # iterating collection, it won't because we're deleting the c++ object and not the python wrapper sip.delete(enemy) self.enemies.clear() self.enemiesCurrentlyAlive = 0 self.enemyExplosionSound.play() # if there are no more enemy tank details that means that the stage is over if not self.enemiesEtds: self.stageEndInitiate() def playerShield(self, pw=None): pw.player.isShielded = True pw.player.shieldTimer.start() playerShieldTimer = QTimer() playerShieldTimer.setTimerType(Qt.PreciseTimer) playerShieldTimer.setInterval(15000) playerShieldTimer.timeout.connect(lambda: self.afterPlayerShield(pw)) playerShieldTimer.start() setattr(self, f"playerShieldTimer{pw.player.id}", playerShieldTimer) def afterPlayerShield(self, pw=None): pw.player.shieldTimer.stop() pw.player.isShielded = False timer = getattr(self, f"playerShieldTimer{pw.player.id}", None) if timer is not None: timer.stop() del timer def upgradeBase(self, pw=None): for baseBlock in self.baseBlocks: if baseBlock.isHidden: self.scene.addItem(baseBlock) baseBlock.isHidden = False baseBlock.type = BlockType.steel baseBlock.updateTexture() self.upgradeBaseTimer = QTimer() self.upgradeBaseTimer.setTimerType(Qt.PreciseTimer) self.upgradeBaseTimer.timeout.connect(self.afterUpgradeBase) self.upgradeBaseTimer.setInterval(15000) self.upgradeBaseTimer.start() def afterUpgradeBase(self, pw=None): for baseBlock in self.baseBlocks: baseBlock.type = BlockType.brick baseBlock.updateTexture() self.upgradeBaseTimer.stop() del self.upgradeBaseTimer def playerLevelUp(self, pw=None): pw.player.levelUp() # check if player is blocked when his size changed, and if it is, put him somewhere where he's not blocked allObjects = self.scene.items() x1 = pw.player.x() y1 = pw.player.y() x2 = x1 + pw.player.width y2 = y1 + pw.player.height for obj in allObjects: oType = type(obj) if obj != pw.player and oType != QGraphicsRectItem: if type(obj) == Block: if obj.type == BlockType.bush or obj.type == BlockType.ice: continue if type(obj) == DeusEx: continue objParent = obj.parentItem() objX1 = 0 objY1 = 0 if objParent is None: objX1 = obj.x() objY1 = obj.y() else: objSceneCoords = obj.mapToScene(obj.pos()) objX1 = objSceneCoords.x() objY1 = objSceneCoords.y() objX2 = objX1 + obj.boundingRect().width() objY2 = objY1 + obj.boundingRect().height() if x1 < objX2 and x2 > objX1 and y1 < objY2 and y2 > objY1: # handle the x axis deltaX = None deltaY = None if x1 < objX1: deltaX = x2 - objX1 x1 -= deltaX elif x1 > objX1: deltaX = objX2 - x1 x1 += deltaX else: deltaX = 0 # handle the y axis if y1 < objY1: deltaY = y2 - objY1 y1 -= (deltaY + 1) elif y1 > objY1: deltaY = objY2 - y1 y1 += deltaY + 1 else: deltaY = 0 # draw in paint these situations and you'll understand why i chose this approach if deltaX > deltaY: pw.player.setY(y1) else: pw.player.setX(x1) continue def playerLifeUp(self, pw=None): pw.player.lives += 1 self.hudPlayersLives[pw.player.id].updateLives(pw.player.lives) def stopTheTime(self, pw=None): self.enemyShootingTimer.stop() for timer in self.enemyMovementTimers.values(): timer.stop() self.stopTheTimeTimer = QTimer() self.stopTheTimeTimer.setTimerType(Qt.PreciseTimer) self.stopTheTimeTimer.timeout.connect(self.afterStopTheTime) self.stopTheTimeTimer.setInterval(10000) self.stopTheTimeTimer.start() def afterStopTheTime(self, pw=None): self.enemyShootingTimer.start() for timer in self.enemyMovementTimers.values(): timer.start() self.stopTheTimeTimer.stop() del self.stopTheTimeTimer # NEGATIVE def playerLevelDown(self, pw=None): pw.player.levelDown() # check if player is blocked when his size changed, and if it is, put him somewhere where he's not blocked allObjects = self.scene.items() x1 = pw.player.x() y1 = pw.player.y() x2 = x1 + pw.player.width y2 = y1 + pw.player.height for obj in allObjects: oType = type(obj) if obj != pw.player and oType != QGraphicsRectItem: if type(obj) == Block: if obj.type == BlockType.bush or obj.type == BlockType.ice: continue if type(obj) == DeusEx: continue objParent = obj.parentItem() objX1 = 0 objY1 = 0 if objParent is None: objX1 = obj.x() objY1 = obj.y() else: objSceneCoords = obj.mapToScene(obj.pos()) objX1 = objSceneCoords.x() objY1 = objSceneCoords.y() objX2 = objX1 + obj.boundingRect().width() objY2 = objY1 + obj.boundingRect().height() if x1 < objX2 and x2 > objX1 and y1 < objY2 and y2 > objY1: # handle the x axis deltaX = None deltaY = None if x1 < objX1: deltaX = x2 - objX1 x1 -= deltaX elif x1 > objX1: deltaX = objX2 - x1 x1 += deltaX else: deltaX = 0 # handle the y axis if y1 < objY1: deltaY = y2 - objY1 y1 -= (deltaY + 1) elif y1 > objY1: deltaY = objY2 - y1 y1 += deltaY + 1 else: deltaY = 0 # draw in paint these situations and you'll understand why i chose this approach if deltaX > deltaY: pw.player.setY(y1) else: pw.player.setX(x1) continue def playerLifeDown(self, pw=None): pw.player.lives -= 1 if pw.player.lives < 0: pw.player.lives = 0 self.hudPlayersLives[pw.player.id].updateLives(pw.player.lives) def playerLosePoints(self, pw=None): pw.player.points -= 1000 if pw.player.points < 0: pw.player.points = 0 def playerCantMove(self, pw): pw.firingNotifier.emitTimer.stop() pw.movementNotifier.emitTimer.stop() playerCantMoveTimer = QTimer() playerCantMoveTimer.setTimerType(Qt.PreciseTimer) playerCantMoveTimer.timeout.connect( lambda: self.afterPlayerCantMove(pw)) playerCantMoveTimer.setInterval(10000) playerCantMoveTimer.start() setattr(self, f"playerCantMoveTimer{pw.player.id}", playerCantMoveTimer) def afterPlayerCantMove(self, pw): pw.firingNotifier.emitTimer.start() pw.movementNotifier.emitTimer.start() timer = getattr(self, f"playerCantMoveTimer{pw.player.id}", None) if timer is not None: timer.stop() del timer def removeBaseShield(self, pw=None): for baseBlock in self.baseBlocks: baseBlock.isHidden = True self.scene.removeItem(baseBlock) self.removeBaseShieldTimer = QTimer() self.removeBaseShieldTimer.setTimerType(Qt.PreciseTimer) self.removeBaseShieldTimer.timeout.connect(self.afterRemoveBaseShield) self.removeBaseShieldTimer.setInterval(15000) self.removeBaseShieldTimer.start() def afterRemoveBaseShield(self): for baseBlock in self.baseBlocks: # this check is needed because upgrade base adds the base blocks to the scene # practically annulling the remove base effect if baseBlock.isHidden: baseBlock.isHidden = False self.scene.addItem(baseBlock) self.removeBaseShieldTimer.stop() del self.removeBaseShieldTimer def startNewStage(self, nextMap, nextStage, gameTypeData, playerData): self.currentMap = nextMap self.currentStage = nextStage self.gameTypeData = gameTypeData self.isOnline = gameTypeData.isOnline self.numOfPlayers = gameTypeData.numOfPlayers self.playerData = playerData self.isGameOver = False self.__init_ui__() self.generateEtd() self.animationTimer.start() self.bulletTimer.start() for timer in self.enemyMovementTimers.values(): timer.start() self.enemyShootingTimer.start() self.enemySpawnTimer.start() del self.gameOver self.gameOver = GameOver(self.config.gameOverTexture) self.gameOver.setPos(QPointF(150, self.fieldBottom + 50)) self.gameOverEmitter.gameOverSignal.connect(self.gameOverHandler) self.generatePlayers() self.currentEnemyCnt = 0 self.enemiesCurrentlyAlive = 0 del self.deusExSpawner self.deusExSpawner = DeusExSpawner(self.scene, self.config, 15000, self.deusExActivate, self.deusExLocations) self.movementSoundHandler.activate()