import sys from dataclasses import dataclass from enum import StrEnum, auto from random import sample from typing import Any, Callable, Iterator, Self from PyQt6 import QtGui from PyQt6.QtCore import QSize, Qt, QTimer from PyQt6.QtGui import QIcon from PyQt6.QtWidgets import ( QApplication, QHBoxLayout, QLabel, QLCDNumber, QMessageBox, QPushButton, QVBoxLayout, QWidget, ) @dataclass(slots=True, frozen=True) class Cords: x: int y: int class ButtonType(StrEnum): EMPTY = auto() BOMB = auto() NUMBER = auto() class Button(QPushButton): def __init__( self, cords: Cords, handler1: Callable[[Self, bool], Any], handler2: Callable[[Self], Any], ) -> None: super().__init__() self.setFixedSize(50, 50) self.type = ButtonType.EMPTY self.number = 0 self.cords = cords self.marked: bool = False self.revealed: bool = False self.left_handler = handler1 self.right_handler = handler2 def reveal(self) -> Iterator[Cords]: self.revealed = True self.setStyleSheet("background-color: grey") if self.type is ButtonType.NUMBER: self.setText(str(self.number)) def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: if e.button() is Qt.MouseButton.LeftButton: self.left_handler(self, True) elif e.button() is Qt.MouseButton.RightButton: self.right_handler(self) def get_neighbors(self) -> Iterator[Cords]: for x in range(self.cords.x - 1, self.cords.x + 2): for y in range(self.cords.y - 1, self.cords.y + 2): if x < 0 or x >= 10: continue if y < 0 or y >= 10: continue if Cords(x, y) == self.cords: continue yield Cords(x, y) def update_texture(self) -> None: if self.marked: self.setIcon(QIcon("flag.jpg")) self.setIconSize(QSize(50, 50)) else: self.setIcon(QIcon()) # TODO: remove if self.type is ButtonType.BOMB: self.setStyleSheet("background-color: red") class Window(QWidget): def __init__(self) -> None: super().__init__() self.setGeometry(0, 0, 500, 500) self.seconds = 0 self.revealed_buttons: set[Cords] = set() self.timer = QTimer() self.timer.timeout.connect(self.show_time) self.timer.setInterval(1000) self.timer.start() self.bomb_amount = 10 self.flags_amount = 0 self.setWindowTitle("Окно") self.create_buttons() self.select_bombs() def create_buttons(self) -> None: vbox = QVBoxLayout() hbox = QHBoxLayout() self.counter = QLabel(f"{self.flags_amount}/{self.bomb_amount}") finish_button = QPushButton() finish_button.setText("FINISH") finish_button.clicked.connect(self.finish) self.time = QLCDNumber() self.time.setDigitCount(5) self.time.display("00:00") hbox.addWidget(self.counter) hbox.addWidget(finish_button) hbox.addWidget(self.time) vbox.addLayout(hbox) self.buttons: list[list[Button]] = [] for x in range(10): hbox = QHBoxLayout() row = [] for y in range(10): button = Button( Cords(x, y), self.button_clicked, self.set_flag, ) row.append(button) hbox.addWidget(button) self.buttons.append(row) vbox.addLayout(hbox) self.setLayout(vbox) def select_bombs(self) -> None: buttons = [j for i in self.buttons for j in i] bombs = sample(buttons, self.bomb_amount) for bomb in bombs: bomb.type = ButtonType.BOMB # TODO: remove bomb.setStyleSheet("background-color: red") for bomb in bombs: for cords in bomb.get_neighbors(): button = self.buttons[cords.x][cords.y] if button.type is ButtonType.BOMB: continue button.type = ButtonType.NUMBER button.number += 1 def button_clicked(self, button: Button, user: bool) -> None: if button.cords in self.revealed_buttons: return self.revealed_buttons.add(button.cords) if button.type is ButtonType.BOMB: if user: QMessageBox.warning(self, "You lost", "You lost") self.close() return button.reveal() if button.type is ButtonType.NUMBER: return for cords in button.get_neighbors(): neighbor = self.buttons[cords.x][cords.y] self.button_clicked(neighbor, False) def set_flag(self, button: Button) -> None: if button.revealed: return was_marked = button.marked button.marked = not was_marked button.update_texture() if was_marked: self.flags_amount -= 1 else: self.flags_amount += 1 self.counter.setText(f"{self.flags_amount}/{self.bomb_amount}") def finish(self) -> None: buttons = [j for i in self.buttons for j in i] bomb = ButtonType.BOMB if not all(button.marked for button in buttons if button.type is bomb): QMessageBox.warning(self, "You lost", "You lost") self.close() return QMessageBox.information(self, "You won", "You won") self.close() def show_time(self) -> None: self.seconds += 1 minutes, seconds = divmod(self.seconds, 60) self.time.display(f"{minutes:0>2}:{seconds:0>2}") if minutes >= 5: QMessageBox.warning(self, "You lost", "You lost") self.close() return def main() -> None: app = QApplication(sys.argv) window = Window() window.show() sys.exit(app.exec()) if __name__ == "__main__": main()