Skip to content

Commit 2c91c19

Browse files
Merge pull request #518 from soumyavemuri/quiz_gui
Quiz Application - GUI
2 parents 9b954a5 + 5f94388 commit 2c91c19

File tree

2 files changed

+307
-0
lines changed

2 files changed

+307
-0
lines changed

Quiz-GUI/README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Quiz Application
2+
3+
A quiz application created with Python's Tkinter GUI toolkit.
4+
5+
## Modules Used
6+
> * tkinter
7+
> * random
8+
> * urllib (request, parse)
9+
> * json
10+
11+
<br/>
12+
13+
## How to Use the Application
14+
Run the below command after switching to the file's directory in the command prompt.
15+
16+
```
17+
$ python quiztime.py
18+
```
19+
<br/>
20+
21+
## About the Application
22+
The quiz consists of 10 questions in total and the user is allowed to select their category preference. The user can choose to attempt the quiz from a range of categories; Movies, Television, Science and Nature, Anime and Manga, Mathematics, General Knowledge, Computers, Cartoons and many more! The user is also allowed to choose the difficulty level they'd like to attempt (Hard, Medium, Easy).
23+
24+
The questions are retrieved from a [Quiz API](https://opentdb.com/api_config.php).
25+
26+
27+
## Developed By
28+
> [Soumya Vemuri](https://github.com/soumyavemuri/)

Quiz-GUI/quiztime.py

+279
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
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

Comments
 (0)