| import sys |
| import math |
| from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, |
| QHBoxLayout, QLabel, QPushButton, QRadioButton, |
| QButtonGroup, QMessageBox, QProgressBar, QFrame) |
| from PyQt5.QtCore import Qt, QTimer |
| from PyQt5.QtGui import QFont, QPalette |
|
|
| class AStarQuizGame(QMainWindow): |
| def __init__(self): |
| super().__init__() |
| self.initUI() |
| self.initQuestions() |
| self.current_question = 0 |
| self.score = 0 |
| self.showQuestion() |
| |
| def initUI(self): |
| self.setWindowTitle("A* Heuristic Functions Quiz Game") |
| self.setGeometry(100, 100, 800, 600) |
| self.setStyleSheet(""" |
| QMainWindow { |
| background: qlineargradient(x1:0, y1:0, x2:1, y2:1, |
| stop:0 #2c3e50, stop:1 #34495e); |
| } |
| QLabel { |
| color: white; |
| background: transparent; |
| } |
| QRadioButton { |
| color: white; |
| background: transparent; |
| padding: 8px; |
| } |
| QRadioButton::indicator { |
| width: 20px; |
| height: 20px; |
| border-radius: 10px; |
| border: 2px solid #3498db; |
| } |
| QRadioButton::indicator:checked { |
| background-color: #3498db; |
| } |
| QPushButton { |
| background-color: #3498db; |
| color: white; |
| border: none; |
| padding: 12px 24px; |
| border-radius: 5px; |
| font-size: 14px; |
| font-weight: bold; |
| } |
| QPushButton:hover { |
| background-color: #2980b9; |
| } |
| QPushButton:disabled { |
| background-color: #7f8c8d; |
| } |
| QFrame { |
| background-color: rgba(44, 62, 80, 0.9); |
| border-radius: 10px; |
| border: 2px solid #3498db; |
| } |
| QProgressBar { |
| border: 2px solid #3498db; |
| border-radius: 5px; |
| text-align: center; |
| color: white; |
| background-color: #2c3e50; |
| } |
| QProgressBar::chunk { |
| background-color: #3498db; |
| width: 20px; |
| } |
| """) |
| |
| |
| central_widget = QWidget() |
| self.setCentralWidget(central_widget) |
| layout = QVBoxLayout(central_widget) |
| layout.setSpacing(20) |
| layout.setContentsMargins(30, 30, 30, 30) |
| |
| |
| title = QLabel("A* Heuristic Functions Quiz") |
| title.setAlignment(Qt.AlignCenter) |
| title.setFont(QFont("Arial", 24, QFont.Bold)) |
| layout.addWidget(title) |
| |
| |
| score_layout = QHBoxLayout() |
| self.score_label = QLabel("Score: 0/0") |
| self.score_label.setFont(QFont("Arial", 14)) |
| self.progress_bar = QProgressBar() |
| self.progress_bar.setMaximum(100) |
| self.progress_bar.setMinimum(0) |
| score_layout.addWidget(self.score_label) |
| score_layout.addWidget(self.progress_bar) |
| layout.addLayout(score_layout) |
| |
| |
| self.question_frame = QFrame() |
| question_frame_layout = QVBoxLayout(self.question_frame) |
| question_frame_layout.setSpacing(15) |
| |
| self.question_label = QLabel() |
| self.question_label.setWordWrap(True) |
| self.question_label.setFont(QFont("Arial", 16)) |
| self.question_label.setAlignment(Qt.AlignCenter) |
| question_frame_layout.addWidget(self.question_label) |
| |
| |
| self.option_group = QButtonGroup() |
| self.options_layout = QVBoxLayout() |
| self.option_buttons = [] |
| for i in range(4): |
| radio = QRadioButton() |
| radio.setFont(QFont("Arial", 12)) |
| self.option_group.addButton(radio, i) |
| self.options_layout.addWidget(radio) |
| self.option_buttons.append(radio) |
| |
| question_frame_layout.addLayout(self.options_layout) |
| layout.addWidget(self.question_frame) |
| |
| |
| buttons_layout = QHBoxLayout() |
| self.prev_button = QPushButton("Previous") |
| self.next_button = QPushButton("Next") |
| self.submit_button = QPushButton("Submit Answer") |
| self.restart_button = QPushButton("Restart Quiz") |
| |
| self.prev_button.clicked.connect(self.previousQuestion) |
| self.next_button.clicked.connect(self.nextQuestion) |
| self.submit_button.clicked.connect(self.submitAnswer) |
| self.restart_button.clicked.connect(self.restartQuiz) |
| |
| buttons_layout.addWidget(self.prev_button) |
| buttons_layout.addWidget(self.next_button) |
| buttons_layout.addWidget(self.submit_button) |
| buttons_layout.addWidget(self.restart_button) |
| layout.addLayout(buttons_layout) |
| |
| |
| self.explanation_label = QLabel() |
| self.explanation_label.setWordWrap(True) |
| self.explanation_label.setFont(QFont("Arial", 12)) |
| self.explanation_label.setStyleSheet("color: #f39c12; background: transparent;") |
| self.explanation_label.setAlignment(Qt.AlignCenter) |
| self.explanation_label.hide() |
| layout.addWidget(self.explanation_label) |
| |
| def initQuestions(self): |
| self.questions = [ |
| { |
| "question": "Which heuristic function is calculated as |xโ - xโ| + |yโ - yโ|?", |
| "options": ["Euclidean Distance", "Manhattan Distance", "Chebyshev Distance", "Hamming Distance"], |
| "correct": 1, |
| "explanation": "Manhattan Distance is calculated as the sum of absolute differences in x and y coordinates: |xโ - xโ| + |yโ - yโ|" |
| }, |
| { |
| "question": "Which heuristic is optimal for grid-based movement with 4-direction movement (up, down, left, right)?", |
| "options": ["Euclidean Distance", "Manhattan Distance", "Chebyshev Distance", "All are equally good"], |
| "correct": 1, |
| "explanation": "Manhattan Distance is optimal for 4-direction grid movement as it exactly matches the movement cost." |
| }, |
| { |
| "question": "What is the formula for Euclidean Distance between points (xโ,yโ) and (xโ,yโ)?", |
| "options": [ |
| "|xโ - xโ| + |yโ - yโ|", |
| "โ((xโ - xโ)ยฒ + (yโ - yโ)ยฒ)", |
| "max(|xโ - xโ|, |yโ - yโ|)", |
| "(xโ - xโ)ยฒ + (yโ - yโ)ยฒ" |
| ], |
| "correct": 1, |
| "explanation": "Euclidean Distance uses the Pythagorean theorem: โ((xโ - xโ)ยฒ + (yโ - yโ)ยฒ)" |
| }, |
| { |
| "question": "Which heuristic is calculated as max(|xโ - xโ|, |yโ - yโ|)?", |
| "options": ["Euclidean Distance", "Manhattan Distance", "Chebyshev Distance", "Minkowski Distance"], |
| "correct": 2, |
| "explanation": "Chebyshev Distance takes the maximum of the absolute differences in coordinates." |
| }, |
| { |
| "question": "For 8-direction movement (including diagonals), which heuristic is most appropriate?", |
| "options": ["Euclidean Distance", "Manhattan Distance", "Chebyshev Distance", "Manhattan is always better"], |
| "correct": 2, |
| "explanation": "Chebyshev Distance is optimal for 8-direction movement as diagonal moves have cost equal to the maximum coordinate difference." |
| }, |
| { |
| "question": "Which heuristic is always admissible (never overestimates) for grid-based pathfinding?", |
| "options": [ |
| "Only Manhattan Distance", |
| "Only Euclidean Distance", |
| "Both Manhattan and Euclidean", |
| "All three: Manhattan, Euclidean, and Chebyshev" |
| ], |
| "correct": 3, |
| "explanation": "All three heuristics are admissible for grid-based pathfinding as they never overestimate the actual cost." |
| }, |
| { |
| "question": "In terms of computation cost, which heuristic is generally the fastest to calculate?", |
| "options": ["Euclidean Distance", "Manhattan Distance", "Chebyshev Distance", "They are all similar"], |
| "correct": 1, |
| "explanation": "Manhattan Distance is fastest as it only uses absolute values and addition, avoiding square roots and powers." |
| }, |
| { |
| "question": "Which heuristic would be most suitable for a game with diagonal movement costing the same as horizontal/vertical?", |
| "options": ["Euclidean Distance", "Manhattan Distance", "Chebyshev Distance", "None are suitable"], |
| "correct": 2, |
| "explanation": "Chebyshev Distance is perfect when diagonal moves cost the same as horizontal/vertical moves." |
| } |
| ] |
| |
| self.user_answers = [None] * len(self.questions) |
| self.answered = [False] * len(self.questions) |
| |
| def showQuestion(self): |
| question_data = self.questions[self.current_question] |
| self.question_label.setText(f"Question {self.current_question + 1}: {question_data['question']}") |
| |
| for i, option in enumerate(question_data['options']): |
| self.option_buttons[i].setText(option) |
| self.option_buttons[i].setChecked(self.user_answers[self.current_question] == i) |
| |
| self.explanation_label.hide() |
| self.updateNavigation() |
| self.updateProgress() |
| |
| def updateNavigation(self): |
| self.prev_button.setEnabled(self.current_question > 0) |
| self.next_button.setEnabled(self.current_question < len(self.questions) - 1) |
| |
| if self.answered[self.current_question]: |
| self.submit_button.setEnabled(False) |
| self.explanation_label.show() |
| else: |
| self.submit_button.setEnabled(True) |
| |
| def updateProgress(self): |
| |
| progress = int((self.current_question + 1) / len(self.questions) * 100) |
| self.progress_bar.setValue(progress) |
| self.score_label.setText(f"Score: {self.score}/{len(self.questions)}") |
| |
| def nextQuestion(self): |
| if self.current_question < len(self.questions) - 1: |
| self.current_question += 1 |
| self.showQuestion() |
| |
| def previousQuestion(self): |
| if self.current_question > 0: |
| self.current_question -= 1 |
| self.showQuestion() |
| |
| def submitAnswer(self): |
| selected = self.option_group.checkedId() |
| if selected == -1: |
| QMessageBox.warning(self, "No Selection", "Please select an answer before submitting.") |
| return |
| |
| self.user_answers[self.current_question] = selected |
| self.answered[self.current_question] = True |
| |
| question_data = self.questions[self.current_question] |
| is_correct = (selected == question_data['correct']) |
| |
| |
| if is_correct: |
| self.score += 1 |
| |
| |
| self.explanation_label.setText( |
| f"{'โ Correct!' if is_correct else 'โ Incorrect!'} {question_data['explanation']}" |
| ) |
| self.explanation_label.show() |
| |
| self.updateNavigation() |
| self.updateProgress() |
| |
| |
| if self.current_question < len(self.questions) - 1: |
| QTimer.singleShot(2000, self.nextQuestion) |
| else: |
| self.showFinalScore() |
| |
| def showFinalScore(self): |
| percentage = (self.score / len(self.questions)) * 100 |
| if percentage >= 80: |
| message = "Excellent! You're an A* heuristic expert! ๐" |
| elif percentage >= 60: |
| message = "Good job! You have a solid understanding! ๐" |
| else: |
| message = "Keep practicing! Review the heuristics and try again! ๐" |
| |
| QMessageBox.information( |
| self, |
| "Quiz Completed!", |
| f"Final Score: {self.score}/{len(self.questions)} ({percentage:.1f}%)\n\n{message}" |
| ) |
| |
| def restartQuiz(self): |
| self.current_question = 0 |
| self.score = 0 |
| self.user_answers = [None] * len(self.questions) |
| self.answered = [False] * len(self.questions) |
| self.option_group.setExclusive(False) |
| for button in self.option_buttons: |
| button.setChecked(False) |
| self.option_group.setExclusive(True) |
| self.showQuestion() |
|
|
| def main(): |
| app = QApplication(sys.argv) |
| |
| |
| font = QFont("Arial", 10) |
| app.setFont(font) |
| |
| game = AStarQuizGame() |
| game.show() |
| |
| sys.exit(app.exec_()) |
|
|
| if __name__ == '__main__': |
| main() |