def setup(self, main_window: QMainWindow) -> None: """ Initialize the UI. :param main_window: An instance of the `QMainWindow` class. :type main_window: :class:`QMainWindow` """ main_window.setObjectName("main_window") main_window.setWindowTitle("TeaseAI") main_window.resize(1137, 751) main_window.setSizePolicy(*EXP_EXP) main_window.setTabShape(QTabWidget.Rounded) self.menubar = QMenuBar(main_window) self.menubar.setObjectName("menubar") self.menubar.setGeometry(0, 0, 1137, 23) self.file_menu = QMenu("File", self.menubar) self.file_menu.setObjectName("file_men") self.server_menu = QMenu("Server", self.menubar) self.server_menu.setObjectName("server_men") self.options_menu = QMenu("Options", self.menubar) self.options_menu.setObjectName("options_men") self.media_menu = QMenu("Media", self.menubar) self.media_menu.setObjectName("media_men") main_window.setMenuBar(self.menubar) self.exit = QAction("Exit", main_window) self.exit.setObjectName("exit") self.start_server = QAction("Start Server", main_window) self.start_server.setObjectName("start_server") self.connect_server = QAction("Connect to Server", main_window) self.connect_server.setObjectName("connect_server") self.kill_server = QAction("Kill Server", main_window) self.kill_server.setObjectName("kill_server") self.options = QAction("Options", main_window) self.options.setObjectName("options") self.start_webcam = QAction("Start Webcam", main_window) self.start_webcam.setObjectName("start_webcam") self.start_webcam.setCheckable(False) self.centralwidget = QWidget(main_window) self.centralwidget.setObjectName("centralwidget") self.centralwidget.setContentsMargins(QMargins(0, 0, 0, 0)) self.centralwidget.setSizePolicy(*EXP_EXP) self.grid_layout = QGridLayout(self.centralwidget) self.media = QFrame(self.centralwidget) self.media.setObjectName("media") self.media.setSizePolicy(*EXP_EXP) self.media.setMinimumSize(200, 200) self.media.setStyleSheet("background: #000;") self.grid_layout.addWidget(self.media, 0, 0, 5, 1) self.users_label = QLabel(" Online users:", self.centralwidget) self.users_label.setObjectName("users_label") self.users_label.setMinimumSize(300, 15) self.users_label.setMaximumSize(300, 15) self.grid_layout.addWidget(self.users_label, 0, 1, 1, 2) self.online = QPlainTextEdit("", self.centralwidget) self.online.setObjectName("online") self.online.setSizePolicy(*FIX_FIX) self.online.setMinimumSize(300, 50) self.online.setMaximumSize(300, 50) self.online.setStyleSheet("margin-left: 3px;" + SUNKEN) self.online.setLineWidth(2) self.online.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.online.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.online.setSizeAdjustPolicy(QAbstractScrollArea.AdjustIgnored) self.online.setReadOnly(True) self.grid_layout.addWidget(self.online, 1, 1, 1, 2) self.chat = QPlainTextEdit("", self.centralwidget) self.chat.setObjectName("chat") self.chat.setSizePolicy(*FIX_EXP) self.chat.setMinimumSize(300, 0) self.chat.setMaximumSize(300, INFINITE) self.chat.setStyleSheet("margin-bottom: 3px; margin-top: 8px;" + SUNKEN) self.chat.setLineWidth(2) self.chat.setReadOnly(True) self.chat.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.grid_layout.addWidget(self.chat, 2, 1, 1, 2) self.input = QLineEdit(self.centralwidget) self.input.setObjectName("input") self.input.setSizePolicy(*FIX_FIX) self.input.setMinimumSize(224, 30) self.input.setMaximumSize(224, 30) self.input.setStyleSheet(SUNKEN) self.input.setEchoMode(QLineEdit.Normal) self.input.setClearButtonEnabled(True) self.grid_layout.addWidget(self.input, 3, 1, 1, 1) self.submit = QPushButton("Submit", self.centralwidget) self.submit.setObjectName("submit") self.submit.setSizePolicy(*FIX_FIX) self.submit.setMinimumSize(70, 30) self.submit.setMaximumSize(70, 30) self.grid_layout.addWidget(self.submit, 3, 2, 1, 1) self.tabs = QTabWidget(self.centralwidget) self.tabs.setObjectName("tabs") self.tabs.setSizePolicy(*FIX_FIX) self.tabs.setMinimumSize(300, 150) self.tabs.setMaximumSize(300, 150) self.tab = QWidget() self.tab.setObjectName("tab") self.tabs.addTab(self.tab, "Actions") self.tab2 = QWidget() self.tab2.setObjectName("tab2") self.tabs.addTab(self.tab2, "My Media") self.tab3 = QWidget() self.tab3.setObjectName("tab3") self.tab3.setSizePolicy(*FIX_FIX) self.grid_layout2 = QGridLayout(self.tab3) self.grid_layout2.setHorizontalSpacing(0) self.grid_layout2.setVerticalSpacing(3) self.grid_layout2.setContentsMargins(3, -1, 3, -1) self.server_folder = QLineEdit(self.tab3) self.server_folder.setObjectName("server_folder") self.grid_layout2.addWidget(self.server_folder, 0, 0, 1, 3) self.srv_browse = QPushButton("BROWSE", self.tab3) self.srv_browse.setObjectName("srv_browse") self.srv_browse.setStyleSheet("background: transparent;\n" " color: #4d4940;\n" " font-size: 8pt;\n" " font-weight: 450;\n" " padding: 6px;\n") self.grid_layout2.addWidget(self.srv_browse, 0, 3, 1, 1) self.back_button = QPushButton("", self.tab3) self.back_button.setObjectName("back_button") self.back_button.setSizePolicy(*FIX_FIX) self.back_button.setMaximumSize(SEVENTY_FIVE) self.back_button.setCursor(QCursor(Qt.PointingHandCursor)) self.back_button.setStyleSheet("border: 0;\n" "background: transparent;") icon = QIcon() icon.addFile(":/newPrefix/back_button.png", SIXTY_FOUR, QIcon.Normal, QIcon.Off) self.back_button.setIcon(icon) self.back_button.setIconSize(SIXTY_FOUR) self.grid_layout2.addWidget(self.back_button, 1, 0, 1, 1) self.play_button = QPushButton("", self.tab3) self.play_button.setObjectName("play_button") self.play_button.setSizePolicy(*FIX_FIX) self.play_button.setMaximumSize(SEVENTY_FIVE) self.play_button.setCursor(QCursor(Qt.PointingHandCursor)) self.play_button.setStyleSheet("border: 0;\n" "background: transparent;") icon1 = QIcon() icon1.addFile(":/newPrefix/play_button.png", SIXTY_FOUR, QIcon.Normal, QIcon.Off) self.play_button.setIcon(icon1) self.play_button.setIconSize(SIXTY_FOUR) self.grid_layout2.addWidget(self.play_button, 1, 1, 1, 1) self.stop_button = QPushButton("", self.tab3) self.stop_button.setObjectName("stop_button") self.stop_button.setSizePolicy(*FIX_FIX) self.stop_button.setMaximumSize(SEVENTY_FIVE) self.stop_button.setCursor(QCursor(Qt.PointingHandCursor)) self.stop_button.setStyleSheet("border: 0;\n" "background: transparent;") icon2 = QIcon() icon2.addFile(":/newPrefix/stop_button.png", SIXTY_FOUR, QIcon.Normal, QIcon.Off) self.stop_button.setIcon(icon2) self.stop_button.setIconSize(SIXTY_FOUR) self.grid_layout2.addWidget(self.stop_button, 1, 2, 1, 1) self.fast_forward = QPushButton("", self.tab3) self.fast_forward.setObjectName("fast_forward") self.fast_forward.setSizePolicy(*FIX_FIX) self.fast_forward.setMaximumSize(SEVENTY_FIVE) self.fast_forward.setCursor(QCursor(Qt.PointingHandCursor)) self.fast_forward.setStyleSheet("border: 0;\n" "background: transparent;") icon3 = QIcon() icon3.addFile(":/newPrefix/fast_forward.png", SIXTY_FOUR, QIcon.Normal, QIcon.Off) self.fast_forward.setIcon(icon3) self.fast_forward.setIconSize(SIXTY_FOUR) self.grid_layout2.addWidget(self.fast_forward, 1, 3, 1, 1) self.tabs.addTab(self.tab3, "Server Media") self.grid_layout.addWidget(self.tabs, 4, 1, 1, 2) main_window.setCentralWidget(self.centralwidget) self.statusbar = QStatusBar(main_window) self.statusbar.setObjectName("statusbar") self.statusbar.setEnabled(True) self.statusbar.setStyleSheet("margin-bottom: 5px;") self.statusbar.setSizePolicy(*EXP_FIX) self.statusbar.setMinimumSize(INFINITE, 30) self.statusbar.setMaximumSize(INFINITE, 30) self.statusbar.setSizeGripEnabled(False) main_window.setStatusBar(self.statusbar) self.menubar.addAction(self.file_menu.menuAction()) self.menubar.addAction(self.server_menu.menuAction()) self.menubar.addAction(self.options_menu.menuAction()) self.menubar.addAction(self.media_menu.menuAction()) self.file_menu.addAction(self.exit) self.server_menu.addAction(self.start_server) self.server_menu.addAction(self.connect_server) self.server_menu.addAction(self.kill_server) self.options_menu.addAction(self.options) self.media_menu.addAction(self.start_webcam) self.exit.triggered.connect(main_window.close) self.tabs.setCurrentIndex(0) QMetaObject.connectSlotsByName(main_window) self.exit.setStatusTip("Exit the program.") self.start_server.setStatusTip("Initialize a local server instance.") self.connect_server.setStatusTip("Connect to a remote server.") self.kill_server.setStatusTip("Shut down a running local server.") self.options.setStatusTip("Open the options menu.") self.start_webcam.setStatusTip("Start webcam feed.") self.tooltip = QLabel("", self.statusbar) tooltip_policy = QSizePolicy(*EXP_FIX) tooltip_policy.setHorizontalStretch(100) self.tooltip.setSizePolicy(tooltip_policy) self.tooltip.setMinimumSize(INFINITE, 26) self.tooltip.setMaximumSize(INFINITE, 26) self.server_status = QLabel("Server status:", self.statusbar) self.server_status.setSizePolicy(*FIX_FIX) self.server_status.setMinimumSize(300, 26) self.server_status.setMaximumSize(300, 26) self.client_status = QLabel("Client status:", self.statusbar) self.client_status.setSizePolicy(*FIX_FIX) self.client_status.setMinimumSize(302, 26) self.client_status.setMaximumSize(302, 26) self.statusbar.addPermanentWidget(self.tooltip) self.statusbar.addPermanentWidget(self.server_status) self.statusbar.addPermanentWidget(self.client_status) self.tooltip.setStyleSheet(SUNKEN + "margin-left: 4px;\ margin-right: 0px;") self.client_status.setStyleSheet(SUNKEN + "margin-right: 7px;") self.server_status.setStyleSheet(SUNKEN + "margin-right: 2px;\ margin-left: 2px;") self.statusbar.messageChanged.connect(main_window.status_tip)
class MainUI(QApplication): def __init__(self, saveDefaultPath: List[str] = None, loadFile: List[str] = None, time: float = None, enableIa: bool = None): super(MainUI, self).__init__(argv) if loadFile: self.game = Game.loadJson(loadFile[0], self.trigger, allowedTime=time) else: self.game = Game(self.trigger, allowedTime=time) self.enableIa = enableIa self.saveDefaultPath = None if not saveDefaultPath or saveDefaultPath.__class__ != list else saveDefaultPath[ 0] self.ia = MinMax(game=self.game) def initialize(self): screen = QGuiApplication.primaryScreen().size() size = screen.height() * 0.7 self.window = QMainWindow() self.window.setAutoFillBackground(True) self.window.setWindowFlag( PySide6.QtCore.Qt.WindowType.WindowMaximizeButtonHint, False) self.window.setWindowTitle('Chess') self.window.resize(size, size) self.window.move((screen.width() - self.window.width()) // 2, (screen.height() - self.window.height()) // 2) self.window.show() self.margin = 10 self.button_size = (size - 2 * self.margin) // 8 def construct(self): self.container = QWidget() self.content = QWidget(parent=self.container) # self.content = QWidget() self.window.setCentralWidget(self.container) self.constructMenu() self.constructButtons() self.constructChoices() self.createClock() self.updateView() self.window.resizeEvent = self.onResize @staticmethod def originalTileColor(i: int, j: int) -> ColorPalette: return ColorPalette.TILE_ONE if i % 2 == j % 2 else ColorPalette.TILE_TWO def onGridClicked(self, i, j): if self.selected != None and (self.selected, (i, j)) in self.previousSuggestions: self.game.move(self.selected, (i, j)) if self.enableIa and not self.game.player: move = self.ia.generate() self.game.move(frm=move[0], to=move[1]) self.selected = None self.previousSuggestions = None if self.game.map[i, j] != Piece.EMPTY: self.selected = (i, j) self.previousSuggestions = self.game.getAvailableMoves(i, j) self.updateView() def onUpgrade(self, pieceIndex): self.game.choice(self.getChoices()[pieceIndex]) def updateView(self, event: QResizeEvent = None): self.content.setEnabled(self.game.winner() == Result.RUNNING) if self.game.upgrade: self.choicesView.show() self.content.setDisabled(True) else: self.choicesView.hide() self.content.setDisabled(False) if not event or event.oldSize().width() - event.size().width(): self.button_size = (self.window.width() - 2 * self.margin) // 8 elif not event or event.oldSize().height() - event.size().height(): self.button_size = (self.window.height() - 2 * self.margin - self.window.menuBar().height() - self.clockContainer.height()) // 8 boardSize = 2 * self.margin + 8 * self.button_size self.container.resize(boardSize, boardSize + self.clockContainer.height()) self.window.resize( self.container.width(), self.container.height() + self.window.menuBar().height()) self.choicesView.resize( len(self.choicesButtons) * (self.margin + self.button_size) + self.margin, self.margin * 2 + self.button_size) self.choicesView.move( (boardSize - self.choicesView.width()) // 2, (self.content.height() - self.choicesView.height()) // 2) self.content.resize(boardSize, boardSize) self.clockContainer.resize(boardSize, self.clockContainer.height()) self.content.move(0, self.clockContainer.height()) self.clockB.move(boardSize - self.clockB.width() - self.margin, self.margin) for i, j in self.buttons: self.buttons[i, j].resize(self.button_size, self.button_size) self.buttons[i, j].move(self.button_size * j + self.margin, self.button_size * i + self.margin) self.buttons[i, j].setIcon(self.game.map[i, j].getIcon()) self.setBackgourdColor(self.buttons[i, j], self.originalTileColor(i, j)) self.buttons[i, j].setIconSize( QSize(self.buttons[i, j].width() * 2 / 3, self.buttons[i, j].height() * 2 / 3)) winner = self.game.winner() if winner != Result.RUNNING: self.showDialog( str(winner) + "\nPlease start a new game.", "Info", QMessageBox.Ok, lambda _: None) return for i in range(len(self.choicesButtons)): self.choicesButtons[i].setIcon(self.getChoices()[i].getIcon()) self.choicesButtons[i].setIconSize( QSize(self.button_size * 2 / 3, self.button_size * 2 / 3)) self.choicesButtons[i].resize(self.button_size, self.button_size) btnW = self.button_size + self.margin self.choicesButtons[i].move( self.margin // 2 + (self.choicesView.width() - btnW * len(self.choicesButtons)) // 2 + btnW * i, (self.choicesView.height() - self.button_size) // 2) if self.selected: self.setBackgourdColor( self.buttons[self.selected[0], self.selected[1]], ColorPalette.ACTIVE_COLOR) if (self.previousSuggestions): for (_, (mi, mj)) in self.previousSuggestions: self.setBackgourdColor( self.buttons[mi, mj], ColorPalette.ATTACK if self.game.map[mi, mj] != Piece.EMPTY else ColorPalette.SUGGESTED_MOVE) if self.game.check(): self.setBackgourdColor( self.buttons[self.game.getPiece( Piece.P1_KING if self.game.player else Piece.P2_KING)], ColorPalette.CHECK) self.undo.setEnabled(len(self.game.undoes) > 0) self.redo.setEnabled(len(self.game.redos) > 0) @staticmethod def setBackgourdColor(widget: QWidget, color: ColorPalette): if 'background-color' in widget.styleSheet(): bk_index_start = widget.styleSheet().index('background-color') bk_index_end = widget.styleSheet().index(';', bk_index_start) + 1 widget.setStyleSheet(widget.styleSheet()[:bk_index_start] + widget.styleSheet()[bk_index_end:] + 'background-color: ' + color.value + ';') else: widget.setStyleSheet(widget.styleSheet() + 'background-color: ' + color.value + ';') def onResize(self, event: QResizeEvent): self.updateView(event) def getChoices(self) -> List[Piece]: return [ Piece(choice.value % Piece.P2_PAWN.value + (0 if self.game.player else Piece.P2_PAWN.value)) for choice in [Piece.P1_ROOK, Piece.P1_KNIGHT, Piece.P1_BISHOP, Piece.P1_QUEEN] ] def trigger(self): self.updateView() def constructChoices(self): self.game.trigger = self.trigger self.choicesView: QWidget = QWidget(parent=self.container) # self.choicesBackground: QWidget = QWidget(parent=self.choicesView) self.choicesButtons: list[QPushButton] = [] self.choicesView.setStyleSheet("border: solid;border-width: 2;") self.setBackgourdColor(widget=self.choicesView, color=ColorPalette.CHOICES_BACKGROUND) for i in range(len(self.getChoices())): self.choicesButtons.append(QPushButton(self.choicesView)) self.choicesButtons[i].clicked.connect( functools.partial(self.onUpgrade, i)) def constructButtons(self): self.buttons: List[QPushButton] = {} self.selected = None self.previousSuggestions = None for i in range(8): for j in range(8): self.buttons[i, j] = QPushButton(self.content) self.buttons[i, j].clicked.connect( functools.partial(self.onGridClicked, i, j)) self.buttons[i, j].setStyleSheet( f"background-color: {self.originalTileColor(i, j).value};") def constructMenu(self): menuBar = QMenuBar() self.window.setMenuBar(menuBar) file = menuBar.addMenu('File') edit = menuBar.addMenu('Edit') file.addAction('New Game').triggered.connect( functools.partial(self.newGame, )) self.undo = edit.addAction('Undo') self.undo.triggered.connect(lambda _: self.game.undo()) self.redo = edit.addAction('Redo') self.redo.triggered.connect(lambda _: self.game.redo()) sv = file.addMenu('Save') sv.addAction('as text').triggered.connect( functools.partial(self.save, self.game.saveAsText)) sv.addAction('as PGN').triggered.connect( functools.partial(self.save, self.game.saveAsPGN)) sv.addAction('as JSON').triggered.connect( functools.partial(self.save, self.game.saveAsJSON)) sva = file.addMenu('Save As') sva.addAction('as text').triggered.connect( functools.partial(self.saveAs, self.game.saveAsText)) sva.addAction('as PGN').triggered.connect( functools.partial(self.saveAs, self.game.saveAsPGN)) sva.addAction('as JSON').triggered.connect( functools.partial(self.saveAs, self.game.saveAsJSON)) file.addAction('Load Game').triggered.connect( functools.partial(self.load, self.game.loadJson)) file.addAction('Abandon').triggered.connect( functools.partial(self.showDialog, "Do you want really to abandon?", "Warning", QMessageBox.No | QMessageBox.Yes, self.abandon)) file.addAction('Draw').triggered.connect( functools.partial(self.showDialog, ("White" if self.game.player else "Black") + " is proposing draw", "Info", QMessageBox.No | QMessageBox.Yes, self.proposeDraw)) file.addAction('Quit').triggered.connect(lambda _: exit(0)) def save(self, f): if self.saveDefaultPath: f(self.saveDefaultPath) else: self.saveAs(f) def saveAs(self, f): saveAs = QFileDialog() saveAs.setDefaultSuffix('.json') self.timer.stop() fileName, _ = saveAs.getSaveFileName(parent=self.window, filter="JSON (*.json)", caption="Save as json file.") self.timer.start(1) if fileName: self.saveDefaultPath = fileName self.save(f) def load(self, f): self.timer.stop() fileName, _ = QFileDialog.getOpenFileName(parent=self.window, filter="JSON (*.json)") self.timer.start() if fileName: self.saveDefaultPath = fileName self.game: Game = f(fileName, trigger=self.trigger, maxTime=self.game.allowedTime) self.updateView() def newGame(self): self.game: Game = Game(self.trigger, allowedTime=self.game.allowedTime) self.construct() def createClock(self): self.clockContainer = QWidget(self.container) self.clockW: QLabel = QLabel(parent=self.clockContainer, text=self.format( self.game.getRemainingTime(True))) self.clockB: QLabel = QLabel(parent=self.clockContainer, text=self.format( self.game.getRemainingTime(False))) self.clockW.move(self.margin, self.margin) self.timer: QTimer = QTimer(self.clockContainer) self.timer.timeout.connect(self.updateTime) self.timer.start(1000) def showDialog(self, text: str, title: str, buttons, callback): dialog = QMessageBox(self.window) dialog.setWindowTitle(title) dialog.setText(text) dialog.setStandardButtons(buttons) dialog.buttonClicked.connect(callback) dialog.exec_() def abandon(self, value: QPushButton): if value.text().lower().count('yes'): self.game.abandon = self.game.player self.updateView() def proposeDraw(self, value: QPushButton): if value.text().lower().count('yes'): self.game.draw = True self.updateView() def updateTime(self): wr = self.game.getRemainingTime(True) br = self.game.getRemainingTime(False) if self.game.winner() == Result.RUNNING: c = wr if self.game.player else br self.timer.start(c - int(c) if c > 0 else 1) else: self.timer.stop() self.updateView() self.clockW.setText(self.format(wr)) self.clockB.setText(self.format(br)) def format(self, time: float): if time <= 0: return "tmout" st = "" if time >= 3600: st += f"{time//3600}:" time = time % 3600 st += '0' if time // 60 < 10 else '' st += f"{time//60}:" time = time % 60 st += '0' if time < 10 else '' st += f"{int(time)}" return st