|
| 1 | +import tkinter as tk |
| 2 | +from tkinter import ttk |
| 3 | +from tkinter import messagebox |
| 4 | +import random |
| 5 | +import urllib.request as request |
| 6 | +import urllib.parse |
| 7 | +import json |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | +class quiz(tk.Tk): |
| 12 | + def __init__(self): |
| 13 | + |
| 14 | + # welcome window |
| 15 | + super().__init__() |
| 16 | + self.s = ttk.Style() |
| 17 | + |
| 18 | + # App title |
| 19 | + self.title("Quiz Time") |
| 20 | + |
| 21 | + # setting dimension for window and placement on user's screen |
| 22 | + self.geometry("900x300+200+200") |
| 23 | + |
| 24 | + # Welcome message |
| 25 | + self.label_1 = tk.Label(self, text="Welcome to the Quiz!", width=30, font=("bold", 15)) |
| 26 | + self.label_1.pack(padx=10, pady=30) |
| 27 | + |
| 28 | + # Start Quiz button |
| 29 | + self.btn_1 = ttk.Button(self, text="Start Quiz", command=lambda: self.label_1.destroy() or self.btn_1.destroy() or self.choices(),) |
| 30 | + self.btn_1.pack() |
| 31 | + |
| 32 | + |
| 33 | + def choices(self): |
| 34 | + # user's category and difficulty level preference are taken here |
| 35 | + |
| 36 | + # initializing the score array to store the user's score. |
| 37 | + self.score = [0]*10 |
| 38 | + # play again gets redirected to this window |
| 39 | + |
| 40 | + # label for category preference |
| 41 | + self.label_1 = tk.Label(self, text="Choose a Category", width=20, font=("bold", 10)) |
| 42 | + self.label_1.place(x=250, y=50) # widget is placed in fixed coordinates using 'place' |
| 43 | + |
| 44 | + # combobox/drop down menu for category preference |
| 45 | + self.category_choice = ttk.Combobox(self, values=["Random Category", "General Knowledge", "Books", "Movies", "Music", "Television", "Video Games", |
| 46 | + "Science and Nature", "Computers", "Mathematics", "Mythology", "Sports", |
| 47 | + "Geography", "History", "Animals", "Celebrities", "Anime and Manga", |
| 48 | + "Cartoons and Animations", "Comics"]) |
| 49 | + |
| 50 | + self.category_choice.place(x=480, y=50) # widget is placed in fixed coordinates |
| 51 | + |
| 52 | + # sets the default choice that's initially displayed |
| 53 | + self.category_choice.current(0) |
| 54 | + |
| 55 | + # label for difficulty preference |
| 56 | + self.label_2 = tk.Label(self, text="Choose a Difficulty Level", width=20, font=("bold", 10)) |
| 57 | + self.label_2.place(x=265, y=100) # widget is placed in fixed coordinates |
| 58 | + |
| 59 | + # combobox/drop down menu for difficulty preference |
| 60 | + self.difficulty_choice = ttk.Combobox(self, values=["Easy", "Medium", "Hard"]) |
| 61 | + self.difficulty_choice.place(x=480, y=100) # widget is placed in fixed coordinates |
| 62 | + |
| 63 | + # sets the default choice that's initially displayed |
| 64 | + self.difficulty_choice.current(1) |
| 65 | + |
| 66 | + # button to go to next window |
| 67 | + self.btn_1 = ttk.Button(self, text="Go", width=10,command=lambda: destroy_widgets() or self.getQuestions()) |
| 68 | + self.btn_1.place(x=450, y=160, anchor='center') # widget is placed in fixed coordinates |
| 69 | + |
| 70 | + def destroy_widgets(): |
| 71 | + # user's category choice and difficulty choice are saved |
| 72 | + self.category = self.category_choice.get() |
| 73 | + self.difficulty = self.difficulty_choice.get() |
| 74 | + |
| 75 | + # all widgets from this window are destroyed |
| 76 | + self.btn_1.destroy() |
| 77 | + self.category_choice.destroy() |
| 78 | + self.difficulty_choice.destroy() |
| 79 | + self.label_1.destroy() |
| 80 | + self.label_2.destroy() |
| 81 | + |
| 82 | + |
| 83 | + def getQuestions(self): |
| 84 | + # Chosen Category and Difficulty level are displayed here for confirmation |
| 85 | + # The user is also allowed to go back and change their preference |
| 86 | + |
| 87 | + # function call to the questions api to retrieve questions |
| 88 | + self.questionsapi(self.category, self.difficulty) |
| 89 | + |
| 90 | + # displays the category chosen by the user |
| 91 | + self.label_1 = tk.Label(self, text="Category: " + self.category, font=('italics', 13)) |
| 92 | + self.label_1.place(x=450, y=50, anchor="center") # widget is placed in fixed coordinates and centered |
| 93 | + |
| 94 | + # displays the difficulty level chosen by the user |
| 95 | + self.label_2 = tk.Label(self, text="Difficulty: "+self.difficulty, font=('italics', 12)) |
| 96 | + self.label_2.place(x=450, y=100, anchor="center") # widget is placed in fixed coordinates and centered |
| 97 | + |
| 98 | + # button redirects the user back to previous window to change their preference |
| 99 | + self.btn_1 = ttk.Button(self, text="Change Choice", command=lambda: destroy_widgets() or self.choices(), width=20) |
| 100 | + self.btn_1.place(x=400, y=150, anchor="e") # widget is placed in fixed coordinates |
| 101 | + |
| 102 | + # button to go to next window, to start playing |
| 103 | + self.btn_2 = ttk.Button(self, text="Next", command=lambda: destroy_widgets() or self.printQuestion(0), width=20) |
| 104 | + self.btn_2.place(x=500, y=150, anchor="w") |
| 105 | + |
| 106 | + def destroy_widgets(): |
| 107 | + # destroy all widgets from this window |
| 108 | + self.btn_1.destroy() |
| 109 | + self.btn_2.destroy() |
| 110 | + self.label_1.destroy() |
| 111 | + self.label_2.destroy() |
| 112 | + |
| 113 | + |
| 114 | + def printQuestion(self, index): |
| 115 | + # function is recursively called to print each question |
| 116 | + # there are a total of 10 questions |
| 117 | + |
| 118 | + if index < 10: |
| 119 | + # label to display question number |
| 120 | + self.label_1 = ttk.Label(self, text="Question "+str(index+1), font=('bold', 11)) |
| 121 | + self.label_1.place(x=450, y=30, anchor="center") |
| 122 | + |
| 123 | + # a label to display the question text |
| 124 | + # wraplength used to make sure the text doesn't flow out of the screen |
| 125 | + self.label_2 = tk.Label(self, text=self.questions[index], font=('bold', 11), wraplength=700, justify=tk.CENTER) |
| 126 | + self.label_2.place(x=450, y=70, anchor="center") |
| 127 | + |
| 128 | + # button to display option 1 |
| 129 | + self.option1 = tk.Button(self, text=self.options[index][0], wraplength=200, justify=tk.CENTER, borderwidth=0.5, relief=tk.SOLID, activebackground='#ddd', |
| 130 | + command=lambda: destroy_widgets() or self.printQuestion(index+1) or self.scoreUpdater(index, 0), width=30) |
| 131 | + self.option1.place(x=250, y=130, anchor="center") |
| 132 | + |
| 133 | + # button to display option 2 |
| 134 | + self.option2 = tk.Button(self, text=self.options[index][1], wraplength=200, justify=tk.CENTER, borderwidth=0.5, relief=tk.SOLID, activebackground='#ddd', |
| 135 | + command=lambda: destroy_widgets() or self.printQuestion(index+1) or self.scoreUpdater(index, 1), width=30) |
| 136 | + self.option2.place(x=650, y=130, anchor="center") |
| 137 | + |
| 138 | + # button to display option 3 |
| 139 | + self.option3 = tk.Button(self, text=self.options[index][2], wraplength=200, justify=tk.CENTER, borderwidth=0.5, relief=tk.SOLID, activebackground='#ddd', |
| 140 | + command=lambda: destroy_widgets() or self.printQuestion(index+1) or self.scoreUpdater(index, 2), width=30) |
| 141 | + self.option3.place(x=250, y=180, anchor="center") |
| 142 | + |
| 143 | + # button to display option 4 |
| 144 | + self.option4 = tk.Button(self, text=self.options[index][3], wraplength=200, justify=tk.CENTER, borderwidth=0.5, relief=tk.SOLID, activebackground='#ddd', |
| 145 | + command=lambda: destroy_widgets() or self.printQuestion(index+1) or self.scoreUpdater(index, 3), width=30) |
| 146 | + self.option4.place(x=650, y=180, anchor="center") |
| 147 | + |
| 148 | + if index > 0: |
| 149 | + # button to navigate to previous question |
| 150 | + # appears from the second question onwards |
| 151 | + self.btn_2 = ttk.Button(self, text="Go to Previous Question", command=lambda: destroy_widgets() or self.printQuestion(index-1)) |
| 152 | + self.btn_2.place(x=70, y=220) |
| 153 | + |
| 154 | + else: |
| 155 | + # once 10 questions have been printed we move onto here |
| 156 | + # a buffer window before we print the score |
| 157 | + |
| 158 | + # a label to notify the user that the quiz is done |
| 159 | + self.label_1 = tk.Label(self, text="Great Work. Hope you had fun!", font=("bold", 12)) |
| 160 | + self.label_1.place(x=450, y=70, anchor="center") # widget is placed in fixed coordinates |
| 161 | + |
| 162 | + # button to navigate to the next page to view score |
| 163 | + self.btn_1 = ttk.Button(self, text="Get Score", command=lambda: self.label_1.destroy() or self.btn_1.destroy() or self.getScore(), width=15) |
| 164 | + self.btn_1.place(x=450, y=130, anchor="center") # widget is placed in fixed coordinates |
| 165 | + |
| 166 | + def destroy_widgets(): |
| 167 | + # destroy all widgets from this window |
| 168 | + self.label_1.destroy() |
| 169 | + self.label_2.destroy() |
| 170 | + self.option1.destroy() |
| 171 | + self.option2.destroy() |
| 172 | + self.option3.destroy() |
| 173 | + self.option4.destroy() |
| 174 | + if index>0: |
| 175 | + self.btn_2.destroy() |
| 176 | + |
| 177 | + def scoreUpdater(self, question, option): |
| 178 | + # function is called every time the user answers a question |
| 179 | + |
| 180 | + # the users answer is compared to the right answer to the question |
| 181 | + # the score array is updated accordingly |
| 182 | + if self.options[question][option] == self.correct_answers[question]: |
| 183 | + self.score[question] = 1 |
| 184 | + else: |
| 185 | + self.score[question] = 0 |
| 186 | + |
| 187 | + def getScore(self): |
| 188 | + # window to display score |
| 189 | + |
| 190 | + # save the user's score as an integer - previously an array |
| 191 | + # count() is used to count the number of correctly answered questions |
| 192 | + self.score = self.score.count(1) |
| 193 | + |
| 194 | + # following if conditions are targeted for a certain score range |
| 195 | + if self.score <= 4: |
| 196 | + self.label_1 = tk.Label(self, text="Better Luck Next Time!", font=("bold", 12)) |
| 197 | + self.label_2 = tk.Label(self, text="Your Score is: " + str(self.score), font=("bold", 12)) |
| 198 | + |
| 199 | + elif self.score == 5: |
| 200 | + self.label_1 = tk.Label(self, text="Not Bad!", font=("bold", 12)) |
| 201 | + self.label_2 = tk.Label(self, text="Your Score is: " + str(self.score), font=("bold", 12)) |
| 202 | + |
| 203 | + elif self.score < 10 and self.score > 5: |
| 204 | + self.label_1 = tk.Label(self, text="Good Job!", font=("bold", 12)) |
| 205 | + self.label_2 = tk.Label(self, text="Your Score is: " + str(self.score), font=("bold", 12)) |
| 206 | + |
| 207 | + elif self.score == 10: |
| 208 | + self.label_1 = tk.Label(self, text="Awesome!", font=("bold", 12)) |
| 209 | + self.label_2 = tk.Label(self, text="Your Score is: " + str(self.score), font=("bold", 12)) |
| 210 | + |
| 211 | + # labels are assigned definite coordinates on the window |
| 212 | + self.label_1.place(x=450, y=70, anchor="center") |
| 213 | + self.label_2.place(x=450, y=120, anchor="center") |
| 214 | + |
| 215 | + # Button to navigate the user to the quiz preferences window to play again |
| 216 | + self.btn_1 = ttk.Button(self, text="Play Again", command=lambda: destroy_widgets() or self.choices()) |
| 217 | + self.btn_1.place(x=400, y=170, anchor="e") |
| 218 | + |
| 219 | + # button to quit |
| 220 | + self.btn_2 = ttk.Button(self, text="Quit", command=lambda: destroy_widgets() or self.destroy()) |
| 221 | + self.btn_2.place(x=500, y=170, anchor="w") |
| 222 | + |
| 223 | + def destroy_widgets(): |
| 224 | + # destroys all widgets from this window |
| 225 | + self.label_1.destroy() |
| 226 | + self.label_2.destroy() |
| 227 | + self.btn_1.destroy() |
| 228 | + self.btn_2.destroy() |
| 229 | + |
| 230 | + def questionsapi(self, category, difficulty): |
| 231 | + # questions for the quiz are retrieved using an api |
| 232 | + # api link https://opentdb.com/api_config.php |
| 233 | + |
| 234 | + # category to ID mapping is made here |
| 235 | + # the full list of category to id mapping can be retrieved here -> https://opentdb.com/api_category.php |
| 236 | + categoryMappings = {"General Knowledge": 9, "Books": 10, "Movies": 11, "Music": 12, "Television": 14, |
| 237 | + "Video Games": 15, "Science and Nature": 17, "Computers": 18, "Mathematics": 19, "Mythology": 20, "Sports": 21, |
| 238 | + "Geography": 22, "History": 23, "Animals": 27, "Celebrities": 26, "Anime and Manga": 31, |
| 239 | + "Cartoons and Animations": 32, "Comics": 29} |
| 240 | + |
| 241 | + # random category is generated in the below if condition |
| 242 | + if category == "Random Category": |
| 243 | + self.category = random.choice(list(categoryMappings.keys())) |
| 244 | + # category is obtained through the category mappings |
| 245 | + category_id = categoryMappings[self.category] |
| 246 | + |
| 247 | + # url to make api call from category and difficulty preferences is generated |
| 248 | + url = 'https://opentdb.com/api.php?amount=10&category=' + \ |
| 249 | + str(category_id) + '&difficulty=' + self.difficulty.lower() + \ |
| 250 | + '&type=multiple&encode=url3986' |
| 251 | + |
| 252 | + # json response is saved using the request module of Python |
| 253 | + with request.urlopen(url) as response: |
| 254 | + source = response.read() |
| 255 | + data = json.loads(source) |
| 256 | + |
| 257 | + # questions, incorrect answers and the correct answers are extracted fromt he response data |
| 258 | + # urllib.parse is used to decode the response data (%20..etc) |
| 259 | + self.questions = [urllib.parse.unquote( q['question'], encoding='utf-8', errors='replace') for q in data['results']] |
| 260 | + self.correct_answers = [urllib.parse.unquote(q['correct_answer'], encoding='utf-8', errors='replace') for q in data['results']] |
| 261 | + |
| 262 | + incorrect_options = [q['incorrect_answers'] for q in data['results']] |
| 263 | + |
| 264 | + # loops through each question's incorrect answers and appends the correct answer to it |
| 265 | + # all 4 options are shuffled usind 'random' module's shuffle |
| 266 | + for i in range(len(incorrect_options)): |
| 267 | + for j in range(len(incorrect_options[i])): |
| 268 | + incorrect_options[i][j] = urllib.parse.unquote(incorrect_options[i][j], encoding='utf-8', errors='replace') |
| 269 | + incorrect_options[i].append(self.correct_answers[i]) |
| 270 | + random.shuffle(incorrect_options[i]) |
| 271 | + |
| 272 | + self.options = [] |
| 273 | + # the |
| 274 | + for i in range(len(incorrect_options)): |
| 275 | + self.options.append(incorrect_options[i]) |
| 276 | + |
| 277 | + |
| 278 | +if __name__ == "__main__": |
| 279 | + quiz().mainloop() |
0 commit comments