This repository has been archived on 2024-08-23. You can view files and clone it, but cannot push or open issues or pull requests.
lessons/Python/soper/soper.py

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()