import math import random import sys from PySide6.QtCore import Qt, QTimer, Signal from PySide6.QtGui import ( QPainter, QColor, QPen, QBrush, QFont, QPainterPath, QLinearGradient, QRadialGradient, QConicalGradient, QPixmap ) from PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QListWidget, QListWidgetItem, QLabel, QTabWidget, QMessageBox, QFrame, QSizePolicy ) from PySide6.QtWidgets import QDialog # ───────────────────────────────────────────────────────────────────────────── # 🎯 برندگان تضمینی - از اینجا تنظیم کنید # کاربران عادی این بخش را نمی‌بینند # ───────────────────────────────────────────────────────────────────────────── GUARANTEED_WINNERS = [ ] # ───────────────────────────────────────────────────────────────────────────── class WinnerDialog(QDialog): def __init__(self, winner: str, parent=None): super().__init__(parent) self.setWindowTitle("🎉 برنده!") self.showFullScreen() self._bg = QPixmap("/home/ali0315/Desktop/3.jpg") # ← مسیر عکست رو اینجا بذار lay = QVBoxLayout(self) lay.setSpacing(20) lay.setContentsMargins(60, 60, 60, 60) # فاصله از بالا برای آوردن متن کمی بالاتر lay.addSpacing(80) name_frame = QFrame() name_frame.setStyleSheet(""" QFrame { background: rgba(0, 0, 0, 150); border-radius: 35px; } """) frame_lay = QVBoxLayout(name_frame) frame_lay.setContentsMargins(60, 30, 60, 30) name_label = QLabel(winner) name_label.setAlignment(Qt.AlignCenter) name_label.setWordWrap(True) name_label.setStyleSheet(""" font-size:160px; font-weight:bold; color:#e3271e; font-family:Tahoma; background:transparent; """) frame_lay.addWidget(name_label) # ارتفاع کارت محدود شود name_frame.setMaximumHeight(300) lay.addWidget(name_frame, 0, Qt.AlignCenter) # فضای خالی پایین تا دکمه پایین بماند lay.addStretch(1) btn = QPushButton("✅ ادامه") btn.setFixedHeight(70) btn.setFixedWidth(300) btn.setStyleSheet(""" QPushButton { background: qlineargradient(x1:0,y1:0,x2:1,y2:1, stop:0 #27ae60, stop:1 #2ecc71); color: white; border: none; border-radius: 14px; font-size: 26px; font-weight: bold; font-family: Tahoma; } QPushButton:hover { background: qlineargradient(x1:0,y1:0,x2:1,y2:1, stop:0 #2ecc71, stop:1 #58d68d); } QPushButton:pressed { background: #1e8449; } """) btn.clicked.connect(self.accept) btn_lay = QHBoxLayout() btn_lay.addStretch() btn_lay.addWidget(btn) btn_lay.addStretch() lay.addLayout(btn_lay) def paintEvent(self, event): painter = QPainter(self) if not self._bg.isNull(): scaled = self._bg.scaled( self.size(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation ) painter.drawPixmap(0, 0, scaled) else: painter.fillRect(self.rect(), QColor("#1a1a2e")) class SpinWheel(QWidget): spin_finished = Signal(str) COLORS = [ "#E74C3C", "#3498DB", "#2ECC71", "#F39C12", "#9B59B6", "#1ABC9C", "#E67E22", "#34495E", "#E91E63", "#00BCD4", "#8BC34A", "#FF5722", ] def __init__(self, parent=None): super().__init__(parent) self.names: list[str] = ["1", "2", "3"] self._angle: float = 0.0 self._spinning = False self._target_angle = 0.0 self._velocity = 0.0 self._timer = QTimer(self) self._timer.timeout.connect(self._tick) self.setMinimumSize(420, 420) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # برنده از پیش تعیین‌شده (مخفی) self._forced_winner: str | None = None # ── public ─────────────────────────────────────────────────────────────── def set_names(self, names: list[str]): self.names = names self._angle = 0 self.update() def spin(self, forced_winner: str | None = None): if self._spinning or not self.names: return self._forced_winner = forced_winner self._spinning = True self._velocity = random.uniform(20, 28) full_rounds = random.randint(5, 9) * 360 if forced_winner and forced_winner in self.names: target_angle = self._calc_angle_for(forced_winner) current_mod = self._angle % 360 diff = (target_angle - current_mod) % 360 self._target_angle = self._angle + full_rounds + diff else: self._target_angle = self._angle + full_rounds + random.uniform(0, 360) self._timer.start(16) def _calc_angle_for(self, name: str) -> float: n = len(self.names) idx = self.names.index(name) slice_deg = 360.0 / n # مرکز بخش idx در فضای چرخ: # mid_qt_angle = 90 - idx*slice_deg - slice_deg/2 # می‌خوایم pointer_in_wheel = mid_qt_angle # pointer_in_wheel = (90 - angle) % 360 # پس: angle = (90 - mid_qt_angle) % 360 mid_qt_angle = 90 - idx * slice_deg - slice_deg / 2 return (90 - mid_qt_angle) % 360 def _tick(self): remaining = self._target_angle - self._angle if remaining <= 0 or self._velocity <= 0.05: self._angle = self._target_angle % 360 self._spinning = False self._timer.stop() self.update() winner = self._winner() self.spin_finished.emit(winner) return decel = max(0.08, remaining / 350) self._velocity = min(self._velocity, decel * 24) self._angle += self._velocity if self._angle >= self._target_angle: self._angle = self._target_angle self.update() def _winner(self) -> str: if not self.names: return "" n = len(self.names) slice_deg = 360.0 / n pointer_in_wheel = (90 - self._angle) % 360 # بخش i از زاویه (90 - i*slice_deg) شروع می‌شه (عقربه ساعت) # پس: i = int((90 - pointer_in_wheel) / slice_deg) # اما باید مراقب منفی شدن باشیم relative = (90 - pointer_in_wheel) % 360 idx = (int(relative / slice_deg) % n) return self.names[idx] def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) side = min(self.width(), self.height()) - 20 cx, cy = self.width() / 2, self.height() / 2 r = side / 2 shadow = QRadialGradient(cx + 6, cy + 6, r) shadow.setColorAt(0, QColor(0, 0, 0, 70)) shadow.setColorAt(1, QColor(0, 0, 0, 0)) painter.setBrush(QBrush(shadow)) painter.setPen(Qt.NoPen) from PySide6.QtCore import QPointF painter.drawEllipse(QPointF(cx + 6, cy + 6), r, r) if not self.names: painter.setBrush(QBrush(QColor("#2C3E50"))) painter.setPen(QPen(QColor("#ECF0F1"), 3)) painter.drawEllipse(QPointF(cx, cy), r, r) painter.setPen(QPen(QColor("#ECF0F1"))) painter.setFont(QFont("Tahoma", 14, QFont.Bold)) painter.drawText(self.rect(), Qt.AlignCenter, "اسامی را وارد کنید") return n = len(self.names) slice_deg = 360.0 / n painter.save() painter.translate(cx, cy) painter.rotate(self._angle) for i, name in enumerate(self.names): color = QColor(self.COLORS[i % len(self.COLORS)]) # هر بخش از i*slice_deg شروع می‌شود start = i * slice_deg grad = QConicalGradient(0, 0, -(start + slice_deg / 2)) grad.setColorAt(0, color.lighter(130)) grad.setColorAt(1, color.darker(120)) painter.setBrush(QBrush(grad)) painter.setPen(QPen(QColor(255, 255, 255, 180), 1.5)) # drawPie: زاویه شروع از راست (ساعت 3) و خلاف عقربه # ما می‌خوایم از بالا (ساعت 12) شروع بشه، پس offset = 90 painter.drawPie( int(-r), int(-r), int(2 * r), int(2 * r), int((90 - start) * 16), int(-slice_deg * 16) ) mid_angle = math.radians(start + slice_deg / 2 - 90) text_r = r * 0.62 tx = text_r * math.cos(mid_angle) ty = text_r * math.sin(mid_angle) painter.save() painter.translate(tx, ty) painter.rotate(start + slice_deg / 2) font = QFont("Tahoma", max(7, int(13 - n * 0.4)), QFont.Bold) painter.setFont(font) painter.setPen(QPen(Qt.white)) fm = painter.fontMetrics() disp = name if len(name) <= 12 else name[:11] + "…" tw = fm.horizontalAdvance(disp) painter.drawText(int(-tw / 2), int(fm.height() / 3), disp) painter.restore() painter.restore() # حلقه بیرونی ring = QLinearGradient(-r, -r, r, r) ring.setColorAt(0, QColor("#F0F0F0")) ring.setColorAt(1, QColor("#A0A0A0")) painter.setBrush(Qt.NoBrush) painter.setPen(QPen(QBrush(ring), 5)) painter.drawEllipse(QPointF(cx, cy), r, r) # مرکز cg = QRadialGradient(cx, cy, 22) cg.setColorAt(0, QColor("#ECF0F1")) cg.setColorAt(1, QColor("#95A5A6")) painter.setBrush(QBrush(cg)) painter.setPen(QPen(QColor("#7F8C8D"), 2)) painter.drawEllipse(QPointF(cx, cy), 22, 22) # اشاره‌گر - بالای چرخ px, py = cx, cy - r - 2 path = QPainterPath() path.moveTo(px, py + 2) path.lineTo(px - 14, py - 28) path.lineTo(px + 14, py - 28) path.closeSubpath() painter.setBrush(QBrush(QColor("#E74C3C"))) painter.setPen(QPen(QColor("#C0392B"), 2)) painter.drawPath(path) # ───────────────────────────────────────────────────────────────────────────── class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("🎡 قرعه‌کشی") self.setMinimumSize(1000, 700) self._apply_theme() self.showFullScreen() # ← این خط self.all_names: list[str] = [] self.disabled_names: set[str] = set() self.winners_log: list[str] = [] self._guaranteed_queue: list[str] = list(GUARANTEED_WINNERS) self._build_ui() # ── تم ─────────────────────────────────────────────────────────────────── def _apply_theme(self): self.setStyleSheet(""" QMainWindow, QWidget { background:#1a1a2e; color:#e0e0e0; font-family:Tahoma; } QTabWidget::pane { border:1px solid #16213e; background:#16213e; border-radius:8px; } QTabBar::tab { background:#0f3460; color:#a0a0c0; padding:10px 22px; border-radius:6px 6px 0 0; margin-right:3px; font-size:13px; } QTabBar::tab:selected { background:#e94560; color:white; font-weight:bold; } QTabBar::tab:hover { background:#533483; } QPushButton { background:qlineargradient(x1:0,y1:0,x2:1,y2:1,stop:0 #e94560,stop:1 #533483); color:white; border:none; border-radius:8px; padding:10px 20px; font-size:13px; font-weight:bold; } QPushButton:hover { background:qlineargradient(x1:0,y1:0,x2:1,y2:1,stop:0 #ff6b81,stop:1 #7b52ab); } QPushButton:pressed { background:#c0392b; } QPushButton:disabled { background:#444; color:#888; } QLineEdit { background:#0f3460; border:2px solid #533483; border-radius:8px; padding:8px 12px; color:#e0e0e0; font-size:13px; } QLineEdit:focus { border-color:#e94560; } QListWidget { background:#0f3460; border:1px solid #533483; border-radius:8px; font-size:12px; outline:none; } QListWidget::item { padding:6px 10px; border-radius:4px; } QListWidget::item:selected { background:#e94560; color:white; } QListWidget::item:hover { background:#533483; } QFrame#card { background:#16213e; border:1px solid #533483; border-radius:12px; padding:10px; } """) # ── UI ─────────────────────────────────────────────────────────────────── def _build_ui(self): central = QWidget() self.setCentralWidget(central) root = QHBoxLayout(central) root.setSpacing(16) root.setContentsMargins(16, 16, 16, 16) # ستون چپ left = QFrame() left.setObjectName("card") left.setFixedWidth(320) left_lay = QVBoxLayout(left) tabs = QTabWidget() left_lay.addWidget(tabs) tabs.addTab(self._tab_names(), "👥 شرکت‌کنندگان") tabs.addTab(self._tab_log(), "📋 لاگ برندگان") root.addWidget(left) # ستون راست right = QFrame() right.setObjectName("card") right_lay = QVBoxLayout(right) right_lay.setSpacing(14) title = QLabel("🎡 چرخ قرعه‌کشی") title.setAlignment(Qt.AlignCenter) title.setStyleSheet("font-size:22px; font-weight:bold; color:#e94560;") right_lay.addWidget(title) self.wheel = SpinWheel() self.wheel.spin_finished.connect(self._on_spin_finished) right_lay.addWidget(self.wheel, 1) self.winner_label = QLabel("برنده: ـــ") self.winner_label.setAlignment(Qt.AlignCenter) self.winner_label.setStyleSheet( "font-size:18px; font-weight:bold; color:#2ecc71;" "background:#0f3460; border-radius:10px; padding:10px;" ) right_lay.addWidget(self.winner_label) btn_row = QHBoxLayout() self.btn_spin = QPushButton("🎰 بچرخون!") self.btn_spin.setFixedHeight(48) self.btn_spin.clicked.connect(self._do_spin) btn_row.addWidget(self.btn_spin) btn_reset = QPushButton("🔄 ریست") btn_reset.setFixedHeight(48) btn_reset.setStyleSheet( "background:qlineargradient(x1:0,y1:0,x2:1,y2:1,stop:0 #2980b9,stop:1 #1abc9c);" ) btn_reset.clicked.connect(self._reset) btn_row.addWidget(btn_reset) right_lay.addLayout(btn_row) root.addWidget(right, 1) def _tab_names(self) -> QWidget: w = QWidget() lay = QVBoxLayout(w) lay.setSpacing(10) lay.addWidget(QLabel("اسامی شرکت‌کنندگان:")) inp_row = QHBoxLayout() self.name_input = QLineEdit() self.name_input.setPlaceholderText("نام را بنویسید…") self.name_input.returnPressed.connect(self._add_name) inp_row.addWidget(self.name_input) btn_add = QPushButton("+") btn_add.setFixedWidth(42) btn_add.clicked.connect(self._add_name) inp_row.addWidget(btn_add) lay.addLayout(inp_row) self.names_list = QListWidget() self.names_list.itemDoubleClicked.connect(self._toggle_disable) lay.addWidget(self.names_list, 1) btn_del = QPushButton("🗑 حذف انتخاب‌شده") btn_del.clicked.connect(self._delete_selected) lay.addWidget(btn_del) hint = QLabel("💡 دوبار کلیک = غیرفعال/فعال کردن") hint.setStyleSheet("color:#888; font-size:11px;") lay.addWidget(hint) return w def _tab_log(self) -> QWidget: w = QWidget() lay = QVBoxLayout(w) self.log_list = QListWidget() lay.addWidget(self.log_list, 1) btn_clear = QPushButton("🧹 پاک کردن لاگ") btn_clear.clicked.connect(self._clear_log) lay.addWidget(btn_clear) return w # ── منطق اسامی ─────────────────────────────────────────────────────────── def _add_name(self, a=None): name = a or self.name_input.text().strip() if not name: return if name in self.all_names: QMessageBox.warning(self, "تکراری", f"«{name}» قبلاً اضافه شده.") return self.all_names.append(name) item = QListWidgetItem(f"✔ {name}") item.setData(Qt.UserRole, name) self.names_list.addItem(item) self.name_input.clear() self._refresh_wheel() def _delete_selected(self): for item in self.names_list.selectedItems(): name = item.data(Qt.UserRole) self.all_names.remove(name) self.disabled_names.discard(name) self.names_list.takeItem(self.names_list.row(item)) self._refresh_wheel() def _toggle_disable(self, item: QListWidgetItem): name = item.data(Qt.UserRole) if name in self.disabled_names: self.disabled_names.discard(name) item.setText(f"✔ {name}") item.setForeground(QColor("#e0e0e0")) else: self.disabled_names.add(name) item.setText(f"✖ {name} [غیرفعال]") item.setForeground(QColor("#e74c3c")) self._refresh_wheel() # ── منطق اصلی قرعه‌کشی ─────────────────────────────────────────────────── def _do_spin(self): active = [n for n in self.all_names if n not in self.disabled_names] if not active: QMessageBox.warning(self, "خالی", "هیچ شرکت‌کننده فعالی وجود ندارد!") return self.btn_spin.setEnabled(False) # بررسی آیا نوبت یک برنده تضمینی است؟ forced = self._pick_forced_winner(active) # چرخ را می‌چرخانیم (forced مخفی است، کاربر نمی‌بیند) self.wheel.spin(forced_winner=forced) def _pick_forced_winner(self, active_names: list[str]) -> str | None: # پاک‌سازی افراد غیرفعال از ابتدای صف while self._guaranteed_queue and self._guaranteed_queue[0] not in active_names: self._guaranteed_queue.pop(0) if not self._guaranteed_queue: return None return self._guaranteed_queue[0] def _on_spin_finished(self, winner: str): self.btn_spin.setEnabled(True) if not winner: return if winner in self._guaranteed_queue: self._guaranteed_queue.remove(winner) self.winner_label.setText(f"🕌 برنده: {winner}") self._log_winner(winner) dlg = WinnerDialog(winner, self) dlg.exec() self.disabled_names.add(winner) self._mark_disabled_in_list(winner) self._refresh_wheel() # ── کمکی ───────────────────────────────────────────────────────────────── def _mark_disabled_in_list(self, name: str): for i in range(self.names_list.count()): item = self.names_list.item(i) if item.data(Qt.UserRole) == name: item.setText(f"✖ {name} [غیرفعال]") item.setForeground(QColor("#e74c3c")) break def _log_winner(self, name: str): self.winners_log.append(name) item = QListWidgetItem(f"{len(self.winners_log)}. {name}") item.setForeground(QColor("#2ecc71")) self.log_list.addItem(item) self.log_list.scrollToBottom() def _clear_log(self): self.log_list.clear() self.winners_log.clear() def _reset(self): reply = QMessageBox.question( self, "ریست", "همه غیرفعال‌سازی‌ها پاک شوند؟", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.disabled_names.clear() self._guaranteed_queue = list(GUARANTEED_WINNERS) for i in range(self.names_list.count()): item = self.names_list.item(i) name = item.data(Qt.UserRole) item.setText(f"✔ {name}") item.setForeground(QColor("#e0e0e0")) self.winner_label.setText("برنده: ـــ") self._refresh_wheel() def _refresh_wheel(self): active = [n for n in self.all_names if n not in self.disabled_names] self.wheel.set_names(active) # ───────────────────────────────────────────────────────────────────────────── if __name__ == "__main__": app = QApplication(sys.argv) app.setLayoutDirection(Qt.RightToLeft) win = MainWindow() for i in range(1, 12): win._add_name(a=str(i)) win.show() sys.exit(app.exec())