208 lines
6.3 KiB
Python
Executable File
208 lines
6.3 KiB
Python
Executable File
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()
|