def on_actItem_Rect(self): # 添加一个矩形 _triggered rect = QRectF(-50, -25, 100, 50) item = QGraphicsRectItem(rect) # x,y 为左上角的图元局部坐标,图元中心点为0,0 item.rect = rect item.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable) item.brush = Qt.yellow item.setBrush(QBrush(item.brush)) item.style = Qt.SolidLine #item.setTransform(QTransform()) self.view.frontZ = self.view.frontZ + 1 item.setZValue(self.view.frontZ) item.setPos(-50 + (QtCore.qrand() % 100), -50 + (QtCore.qrand() % 100)) self.view.seqNum = self.view.seqNum + 1 item.setData(self.view.ItemId, self.view.seqNum) item.setData(self.view.ItemDesciption, "矩形") self.scene.addItem(item) self.scene.clearSelection() item.setSelected(True)
class NodeTemplateItem(): ''' This represents one node template on the diagram. A node template can be on many diagrams This class creates the rectangle graphics item and the text graphics item and adds them to the scene. ''' def __init__(self, scene, x, y, nodeTemplateDict=None, NZID=None): self.scene = scene self.logMsg = None self.x = x self.y = y self.nodeTemplateDict = nodeTemplateDict # self.name = self.nodeTemplateDict.get("name", "") THIS HAS BEEN REPLACED BY THE name FUNCTION - SEE BELOW self.diagramType = "Node Template" self.displayText = None self.model = self.scene.parent.model self.gap = 100 self.relList = [] # assign a unique key if it doesn't already have one if NZID == None: self.NZID = str(uuid.uuid4()) else: self.NZID = NZID # init graphics objects to none self.TNode = None self.TNtext = None # draw the node template on the diagram self.drawIt() def name(self, ): return self.nodeTemplateDict.get("name", "") def getX(self, ): return self.TNode.boundingRect().x() def getY(self, ): return self.TNode.boundingRect().y() def getHeight(self, ): return self.TNode.boundingRect().height() def getWidth(self, ): return self.TNode.boundingRect().width() def getRelList(self, ): '''return a list of all relationitems that are inbound or outbound from this node template. do not include self referencing relationships ''' return [ diagramItem for key, diagramItem in self.scene.parent.itemDict.items() if diagramItem.diagramType == "Relationship Template" and ( diagramItem.startNZID == self.NZID or diagramItem.endNZID == self.NZID) ] def getPoint(self, offset=None): ''' This function is used by the template diagram to calculate the location to drop a node template on the diagram ''' if offset is None: return QPointF(self.x, self.y) else: return QPointF(self.x + offset, self.y + offset) def getFormat(self, ): ''' determine if the Node Template has a template format or should use the project default format ''' # get the node Template custom format customFormat = self.nodeTemplateDict.get("TNformat", None) if not customFormat is None: # get the template custom format self.nodeFormat = TNodeFormat(formatDict=customFormat) else: # get the project default format self.nodeFormat = TNodeFormat( formatDict=self.model.modelData["TNformat"]) def clearItem(self, ): if (not self.TNode is None and not self.TNode.scene() is None): self.TNode.scene().removeItem(self.TNode) if (not self.TNtext is None and not self.TNtext.scene() is None): self.TNtext.scene().removeItem(self.TNtext) def drawIt(self, ): # get current format as it may have changed self.getFormat() # create the qgraphicsItems if they don't exist if self.TNode is None: # create the rectangle self.TNode = QGraphicsRectItem(QRectF( self.x, self.y, self.nodeFormat.formatDict["nodeWidth"], self.nodeFormat.formatDict["nodeHeight"]), parent=None) self.TNode.setZValue(NODELAYER) self.TNode.setFlag(QGraphicsItem.ItemIsMovable, True) self.TNode.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.TNode.setFlag(QGraphicsItem.ItemIsSelectable, True) self.TNode.setSelected(True) self.TNode.setData(1, self.NZID) # get with self.INode.data(1) self.TNode.setData(ITEMTYPE, NODETEMPLATE) # create the text box self.TNtext = QGraphicsTextItem("", parent=None) self.TNtext.setPos(self.x, self.y) self.TNtext.setFlag(QGraphicsItem.ItemIsMovable, True) self.TNtext.setFlag(QGraphicsItem.ItemIsSelectable, False) self.TNtext.setData(NODEID, self.NZID) self.TNtext.setData(ITEMTYPE, NODETEMPLATETEXT) self.TNtext.setZValue(NODELAYER) # save the location self.x = self.TNode.sceneBoundingRect().x() self.y = self.TNode.sceneBoundingRect().y() # generate the html and resize the rectangle self.formatItem() # add the graphics items to the scene self.scene.addItem(self.TNode) self.scene.addItem(self.TNtext) else: # generate the html and resize the rectangle self.formatItem() def formatItem(self, ): # configure the formatting aspects of the qgraphics item pen = self.nodeFormat.pen() brush = self.nodeFormat.brush() self.TNode.setBrush(brush) self.TNode.setPen(pen) # generate the HTML genHTML = self.generateHTML() self.TNtext.prepareGeometryChange() # print("before html bounding rectangle width:{}".format(self.TNtext.boundingRect().width())) # print("before html text width:{}".format(self.TNtext.textWidth())) self.TNtext.setTextWidth( -1 ) # reset the width to unkonwn so it will calculate a new width based on the new html self.TNtext.setHtml(genHTML) # print("after html bounding rectangle width:{}".format(self.TNtext.boundingRect().width())) # print("after html text width:{}".format(self.TNtext.textWidth())) # make sure minimum width of 120 if self.TNtext.boundingRect().width() < 120: self.TNtext.setTextWidth(120) else: self.TNtext.setTextWidth( self.TNtext.boundingRect().width() ) # you have to do a setTextWidth to get the html to render correctly. # set the rectangle item to the same size as the formatted html self.TNode.prepareGeometryChange() currentRect = self.TNode.rect() # insure minimum height of 120 if self.TNtext.boundingRect().height() < 120: currentRect.setHeight(120) else: currentRect.setHeight(self.TNtext.boundingRect().height()) currentRect.setWidth(self.TNtext.boundingRect().width()) self.TNode.setRect(currentRect) def generateHTML(self, ): ''' Generate the HTML that formats the node template data inside the rectangle ''' # generate the html prefix = "<!DOCTYPE html><html><body>" # head = "<head><style>table, th, td {border: 1px solid black; border-collapse: collapse;}</style></head>" suffix = "</body></html>" # blankRow = "<tr><td><left>{}</left></td><td><left>{}</left></td><td><left>{}</left></td><td><left>{}</left></td></tr>".format("", "", "", "") name = "<center><b>{}</b></center>".format( self.nodeTemplateDict.get("name", "")) lbls = self.genLblHTML() props = self.genPropHTML() genHTML = "{}{}<hr>{}<br><hr>{}{}".format(prefix, name, lbls, props, suffix) # print("{} html: {}".format(self.name(), genHTML)) return genHTML def genLblHTML(self): # html = '<table width="90%">' html = '<table style="width:90%;border:1px solid black;">' if len(self.nodeTemplateDict.get("labels", [])) > 0: for lbl in self.nodeTemplateDict.get("labels", []): if lbl[NODEKEY] == Qt.Checked: nk = "NK" else: nk = " " if lbl[REQUIRED] == Qt.Checked: rq = "R" else: rq = "" html = html + '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format( nk, lbl[LABEL], "", rq) html = html + "</table>" else: html = '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format( " ", "NO{}LABELS".format(" "), "", "") html = html + "</table>" return html def genPropHTML(self): # PROPERTY, DATATYPE, PROPREQ, DEFAULT, EXISTS, UNIQUE, PROPNODEKEY html = '<table style="width:90%;border:1px solid black;">' if len(self.nodeTemplateDict.get("properties", [])) > 0: for prop in self.nodeTemplateDict.get("properties", []): if prop[PROPNODEKEY] == Qt.Checked: nk = "NK" else: nk = " " if prop[PROPREQ] == Qt.Checked: rq = "R" else: rq = "" if prop[EXISTS] == Qt.Checked: ex = "E" else: ex = "" if prop[UNIQUE] == Qt.Checked: uq = "U" else: uq = "" html = html + '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format( nk, prop[PROPERTY], rq, ex, uq) html = html + "</table>" else: html = html + '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format( " ", "NO{}PROPERTIES".format(" "), "", "", "") html = html + "</table>" return html def moveIt(self, dx, dy): ''' Move the node rectangle and the node textbox to the delta x,y coordinate. ''' # print("before moveIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect())) self.TNode.moveBy(dx, dy) self.x = self.TNode.sceneBoundingRect().x() self.y = self.TNode.sceneBoundingRect().y() self.TNtext.moveBy(dx, dy) # print("after moveIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect())) # now redraw all the relationships self.drawRels() def drawRels(self, ): '''Redraw all the relationship lines connected to the Node Template Rectangle''' # get a list of the relationship items connected to this node template self.relList = self.getRelList() # assign the correct inbound/outbound side for the rel for rel in self.relList: if rel.endNodeItem.NZID != rel.startNodeItem.NZID: # ignore bunny ears rel.assignSide() # get a set of all the nodes and sides involved nodeSet = set() for rel in self.relList: if rel.endNodeItem.NZID != rel.startNodeItem.NZID: # ignore bunny ears nodeSet.add((rel.endNodeItem, rel.inboundSide)) nodeSet.add((rel.startNodeItem, rel.outboundSide)) # tell each node side to assign rel locations for nodeSide in nodeSet: nodeSide[0].assignPoint(nodeSide[1]) ############################################ # now tell them all to redraw for rel in self.relList: rel.drawIt2() def calcOffset(self, index, totRels): offset = [-60, -40, -20, 0, 20, 40, 60] offsetStart = [3, 2, 2, 1, 1, 0, 0] if totRels > 7: totRels = 7 return offset[offsetStart[totRels - 1] + index] def assignPoint(self, side): # go through all the rels on a side and assign their x,y coord for that side self.relList = self.getRelList() sideList = [ rel for rel in self.relList if ((rel.startNZID == self.NZID and rel.outboundSide == side) or ( rel.endNZID == self.NZID and rel.inboundSide == side)) ] totRels = len(sideList) if totRels > 0: if side == R: # calc center of the side x = self.x + self.getWidth() y = self.y + self.getHeight() / 2 # sort the rels connected to this side by the y value sideList.sort(key=self.getSortY) # assign each of them a position on the side starting in the center and working out in both directions for index, rel in enumerate(sideList): if rel.startNZID == self.NZID: rel.outboundPoint = QPointF( x, y + (self.calcOffset(index, totRels))) if rel.endNZID == self.NZID: rel.inboundPoint = QPointF( x, y + (self.calcOffset(index, totRels))) elif side == L: x = self.x y = self.y + self.getHeight() / 2 sideList.sort(key=self.getSortY) for index, rel in enumerate(sideList): if rel.startNZID == self.NZID: rel.outboundPoint = QPointF( x, y + (self.calcOffset(index, totRels))) if rel.endNZID == self.NZID: rel.inboundPoint = QPointF( x, y + (self.calcOffset(index, totRels))) elif side == TOP: x = self.x + self.getWidth() / 2 y = self.y sideList.sort(key=self.getSortX) for index, rel in enumerate(sideList): if rel.startNZID == self.NZID: rel.outboundPoint = QPointF( x + (self.calcOffset(index, totRels)), y) if rel.endNZID == self.NZID: rel.inboundPoint = QPointF( x + (self.calcOffset(index, totRels)), y) elif side == BOTTOM: x = self.x + self.getWidth() / 2 y = self.y + self.getHeight() sideList.sort(key=self.getSortX) for index, rel in enumerate(sideList): if rel.startNZID == self.NZID: rel.outboundPoint = QPointF( x + (self.calcOffset(index, totRels)), y) if rel.endNZID == self.NZID: rel.inboundPoint = QPointF( x + (self.calcOffset(index, totRels)), y) else: print("error, no side") def getSortY(self, rel): # if this node is the start node then return the end node's Y if rel.startNZID == self.NZID: return rel.endNodeItem.TNode.sceneBoundingRect().center().y() # if this node is the end node then return the start node's Y if rel.endNZID == self.NZID: return rel.startNodeItem.TNode.sceneBoundingRect().center().y() # this should never happen return 0 def getSortX(self, rel): # if this node is the start node then return the end node's X if rel.startNZID == self.NZID: return rel.endNodeItem.TNode.sceneBoundingRect().center().x() # if this node is the end node then return the start node's X if rel.endNZID == self.NZID: return rel.startNodeItem.TNode.sceneBoundingRect().center().x() # this should never happen return 0 def getObjectDict(self, ): ''' This function returns a dictionary with all the data that represents this node template item. The dictionary is added to the Instance Diagram dictionary.''' objectDict = {} objectDict["NZID"] = self.NZID objectDict["name"] = self.nodeTemplateDict.get("name", "") objectDict["displayText"] = self.displayText objectDict["x"] = self.TNode.sceneBoundingRect().x() objectDict["y"] = self.TNode.sceneBoundingRect().y() objectDict["diagramType"] = self.diagramType objectDict["labels"] = self.nodeTemplateDict.get("labels", []) objectDict["properties"] = self.nodeTemplateDict.get("properties", []) return objectDict def setLogMethod(self, logMethod=None): if logMethod is None: if self.logMsg is None: self.logMsg = self.noLog else: self.logMsg = logMethod def noLog(self, msg): return
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})")