quastt-show 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quastt_show-0.1.2/PKG-INFO +11 -0
- quastt_show-0.1.2/quastt_show/__init__.py +1 -0
- quastt_show-0.1.2/quastt_show/game.py +388 -0
- quastt_show-0.1.2/quastt_show.egg-info/PKG-INFO +11 -0
- quastt_show-0.1.2/quastt_show.egg-info/SOURCES.txt +9 -0
- quastt_show-0.1.2/quastt_show.egg-info/dependency_links.txt +1 -0
- quastt_show-0.1.2/quastt_show.egg-info/entry_points.txt +2 -0
- quastt_show-0.1.2/quastt_show.egg-info/requires.txt +1 -0
- quastt_show-0.1.2/quastt_show.egg-info/top_level.txt +1 -0
- quastt_show-0.1.2/setup.cfg +4 -0
- quastt_show-0.1.2/setup.py +17 -0
@@ -0,0 +1 @@
|
|
1
|
+
from .game import Game
|
@@ -0,0 +1,388 @@
|
|
1
|
+
import tkinter as tk
|
2
|
+
from tkinter import messagebox
|
3
|
+
import requests
|
4
|
+
import html
|
5
|
+
import random
|
6
|
+
import json
|
7
|
+
import os
|
8
|
+
from tkinter import PhotoImage
|
9
|
+
# ------------------------------
|
10
|
+
# Constants and styles
|
11
|
+
# ------------------------------
|
12
|
+
|
13
|
+
BG_COLOR = "#f0f4f8"
|
14
|
+
PANEL_BG = "#ffffff"
|
15
|
+
ACCENT_COLOR = "#4a90e2"
|
16
|
+
TEXT_COLOR = "#333333"
|
17
|
+
CORRECT_COLOR = "#28a745"
|
18
|
+
WRONG_COLOR = "#dc3545"
|
19
|
+
FONT_LARGE = ("Segoe UI", 24, "bold")
|
20
|
+
FONT_MEDIUM = ("Segoe UI", 16)
|
21
|
+
FONT_SMALL = ("Segoe UI", 12)
|
22
|
+
|
23
|
+
STATS_FILE = os.path.join(os.path.expanduser("~"), ".trivia_stats.json")
|
24
|
+
|
25
|
+
|
26
|
+
# ------------------------------
|
27
|
+
# Rounded Button using Canvas
|
28
|
+
# ------------------------------
|
29
|
+
|
30
|
+
class RoundedButton(tk.Canvas):
|
31
|
+
def __init__(self, parent, text, command=None, width=200, height=50, radius=20, bg=ACCENT_COLOR, fg="white", font=FONT_MEDIUM):
|
32
|
+
super().__init__(parent, width=width, height=height, highlightthickness=0, bg=parent["bg"])
|
33
|
+
self.command = command
|
34
|
+
self.radius = radius
|
35
|
+
self.bg = bg
|
36
|
+
self.fg = fg
|
37
|
+
self.font = font
|
38
|
+
self.text = text
|
39
|
+
self.width = width
|
40
|
+
self.height = height
|
41
|
+
|
42
|
+
self.create_rounded_rect(0, 0, width, height, radius, fill=bg, outline="")
|
43
|
+
self.text_id = self.create_text(width // 2, height // 2, text=text, fill=fg, font=font)
|
44
|
+
|
45
|
+
self.bind("<Button-1>", self._on_click)
|
46
|
+
self.bind("<Enter>", self._on_enter)
|
47
|
+
self.bind("<Leave>", self._on_leave)
|
48
|
+
|
49
|
+
def create_rounded_rect(self, x1, y1, x2, y2, r, **kwargs):
|
50
|
+
points = [
|
51
|
+
x1+r, y1,
|
52
|
+
x2-r, y1,
|
53
|
+
x2, y1,
|
54
|
+
x2, y1+r,
|
55
|
+
x2, y2-r,
|
56
|
+
x2, y2,
|
57
|
+
x2-r, y2,
|
58
|
+
x1+r, y2,
|
59
|
+
x1, y2,
|
60
|
+
x1, y2-r,
|
61
|
+
x1, y1+r,
|
62
|
+
x1, y1,
|
63
|
+
]
|
64
|
+
return self.create_polygon(points, smooth=True, **kwargs)
|
65
|
+
|
66
|
+
def _on_click(self, event):
|
67
|
+
if self.command:
|
68
|
+
self.command()
|
69
|
+
|
70
|
+
def _on_enter(self, event):
|
71
|
+
self.itemconfig(1, fill="#357ABD") # Slightly darker on hover
|
72
|
+
|
73
|
+
def _on_leave(self, event):
|
74
|
+
self.itemconfig(1, fill=self.bg)
|
75
|
+
|
76
|
+
|
77
|
+
# ------------------------------
|
78
|
+
# Main Game class
|
79
|
+
# ------------------------------
|
80
|
+
|
81
|
+
class Game:
|
82
|
+
def __init__(self, difficulty="medium"):
|
83
|
+
self.difficulty = difficulty
|
84
|
+
self.app = TriviaApp(difficulty)
|
85
|
+
|
86
|
+
def run(self):
|
87
|
+
self.app.mainloop()
|
88
|
+
|
89
|
+
|
90
|
+
# ------------------------------
|
91
|
+
# TriviaApp Main Tk window
|
92
|
+
# ------------------------------
|
93
|
+
|
94
|
+
class TriviaApp(tk.Tk):
|
95
|
+
def __init__(self, difficulty):
|
96
|
+
super().__init__()
|
97
|
+
self.title("QuasttShow")
|
98
|
+
self.geometry("1200x800")
|
99
|
+
self.configure(bg=BG_COLOR)
|
100
|
+
self.resizable(False, False)
|
101
|
+
|
102
|
+
self.difficulty = difficulty
|
103
|
+
self.questions = []
|
104
|
+
self.current_q_index = 0
|
105
|
+
self.score = 0
|
106
|
+
self.streak = 0
|
107
|
+
self.max_streak = 0
|
108
|
+
self.paused = False
|
109
|
+
self.ai_stats = {"correct_by_category": {}, "questions_answered": 0, "correct_answers": 0}
|
110
|
+
|
111
|
+
self.stats = self.load_stats()
|
112
|
+
|
113
|
+
self.container = tk.Frame(self, bg=BG_COLOR)
|
114
|
+
self.container.pack(expand=True, fill="both")
|
115
|
+
|
116
|
+
self.frames = {}
|
117
|
+
for F in (MenuFrame, GameFrame, StatsFrame, PauseOverlay):
|
118
|
+
frame = F(self.container, self)
|
119
|
+
self.frames[F.__name__] = frame
|
120
|
+
frame.place(relwidth=1, relheight=1)
|
121
|
+
|
122
|
+
self.show_frame("MenuFrame")
|
123
|
+
|
124
|
+
def show_frame(self, name):
|
125
|
+
frame = self.frames[name]
|
126
|
+
frame.tkraise()
|
127
|
+
|
128
|
+
def load_stats(self):
|
129
|
+
if os.path.exists(STATS_FILE):
|
130
|
+
try:
|
131
|
+
with open(STATS_FILE, "r") as f:
|
132
|
+
return json.load(f)
|
133
|
+
except Exception:
|
134
|
+
return {"games_played": 0, "total_score": 0, "max_score": 0, "max_streak": 0}
|
135
|
+
else:
|
136
|
+
return {"games_played": 0, "total_score": 0, "max_score": 0, "max_streak": 0}
|
137
|
+
|
138
|
+
def save_stats(self):
|
139
|
+
try:
|
140
|
+
with open(STATS_FILE, "w") as f:
|
141
|
+
json.dump(self.stats, f)
|
142
|
+
except Exception:
|
143
|
+
pass
|
144
|
+
|
145
|
+
def fetch_questions(self, amount=15, difficulty=None, qtype='multiple'):
|
146
|
+
difficulty = difficulty or self.difficulty
|
147
|
+
url = f"https://opentdb.com/api.php?amount={amount}&difficulty={difficulty}&type={qtype}"
|
148
|
+
try:
|
149
|
+
response = requests.get(url)
|
150
|
+
data = response.json()
|
151
|
+
if data["response_code"] != 0:
|
152
|
+
return []
|
153
|
+
return data["results"]
|
154
|
+
except Exception:
|
155
|
+
return []
|
156
|
+
|
157
|
+
def start_new_game(self):
|
158
|
+
self.questions = self.fetch_questions()
|
159
|
+
if not self.questions:
|
160
|
+
messagebox.showerror("Error", "Failed to fetch questions. Check your internet connection.")
|
161
|
+
self.show_frame("MenuFrame")
|
162
|
+
return
|
163
|
+
self.current_q_index = 0
|
164
|
+
self.score = 0
|
165
|
+
self.streak = 0
|
166
|
+
self.max_streak = 0
|
167
|
+
self.ai_stats = {"correct_by_category": {}, "questions_answered": 0, "correct_answers": 0}
|
168
|
+
self.paused = False
|
169
|
+
self.frames["GameFrame"].load_question()
|
170
|
+
self.show_frame("GameFrame")
|
171
|
+
|
172
|
+
def update_game_stats(self, correct, category):
|
173
|
+
self.ai_stats['questions_answered'] += 1
|
174
|
+
if correct:
|
175
|
+
self.ai_stats['correct_answers'] += 1
|
176
|
+
self.ai_stats['correct_by_category'][category] = \
|
177
|
+
self.ai_stats['correct_by_category'].get(category, 0) + 1
|
178
|
+
|
179
|
+
def end_game(self):
|
180
|
+
self.stats["games_played"] += 1
|
181
|
+
self.stats["total_score"] += self.score
|
182
|
+
self.stats["max_score"] = max(self.stats.get("max_score", 0), self.score)
|
183
|
+
self.stats["max_streak"] = max(self.stats.get("max_streak", 0), self.max_streak)
|
184
|
+
self.save_stats()
|
185
|
+
msg = f"Game Over!\nFinal Score: {self.score}\nMax Streak: {self.max_streak}"
|
186
|
+
messagebox.showinfo("Game Over", msg)
|
187
|
+
self.show_frame("MenuFrame")
|
188
|
+
|
189
|
+
|
190
|
+
# ------------------------------
|
191
|
+
# Frames: Menu, Game, Pause, Stats
|
192
|
+
# ------------------------------
|
193
|
+
|
194
|
+
class MenuFrame(tk.Frame):
|
195
|
+
def __init__(self, parent, controller):
|
196
|
+
super().__init__(parent, bg=BG_COLOR)
|
197
|
+
self.controller = controller
|
198
|
+
|
199
|
+
title = tk.Label(self, text="🎲Enter QuasttShow", font=FONT_LARGE, fg=ACCENT_COLOR, bg=BG_COLOR)
|
200
|
+
title.pack(pady=(80, 40))
|
201
|
+
btn_start = RoundedButton(self, "🏐Start Game", command=self.start_game, width=300, height=60)
|
202
|
+
btn_start.pack(pady=20)
|
203
|
+
|
204
|
+
btn_stats = RoundedButton(self, "✨Statistics", command=lambda: controller.show_frame("StatsFrame"), width=300, height=60)
|
205
|
+
btn_stats.pack(pady=20)
|
206
|
+
|
207
|
+
btn_exit = RoundedButton(self, "❌Exit", command=controller.destroy, width=300, height=60)
|
208
|
+
btn_exit.pack(pady=20)
|
209
|
+
|
210
|
+
def start_game(self):
|
211
|
+
self.controller.start_new_game()
|
212
|
+
|
213
|
+
|
214
|
+
class GameFrame(tk.Frame):
|
215
|
+
def __init__(self, parent, controller):
|
216
|
+
super().__init__(parent, bg=BG_COLOR)
|
217
|
+
self.controller = controller
|
218
|
+
|
219
|
+
# Question text
|
220
|
+
self.question_text = tk.Label(self, text="", font=FONT_MEDIUM, fg=TEXT_COLOR, bg=BG_COLOR, wraplength=1000, justify="center")
|
221
|
+
self.question_text.pack(pady=(60, 30))
|
222
|
+
|
223
|
+
# Answers frame (2 columns)
|
224
|
+
self.answers_frame = tk.Frame(self, bg=BG_COLOR)
|
225
|
+
self.answers_frame.pack(pady=20)
|
226
|
+
|
227
|
+
# Status bar (score, streak)
|
228
|
+
self.status_label = tk.Label(self, text="", font=FONT_SMALL, fg=TEXT_COLOR, bg=BG_COLOR)
|
229
|
+
self.status_label.pack(pady=10)
|
230
|
+
|
231
|
+
# AI prediction
|
232
|
+
self.ai_label = tk.Label(self, text="", font=FONT_SMALL, fg=ACCENT_COLOR, bg=BG_COLOR, justify="center")
|
233
|
+
self.ai_label.pack(pady=10)
|
234
|
+
|
235
|
+
# Pause button
|
236
|
+
self.pause_btn = RoundedButton(self, "Pause", command=self.show_pause, width=150, height=40)
|
237
|
+
self.pause_btn.pack(pady=15)
|
238
|
+
|
239
|
+
self.answer_buttons = []
|
240
|
+
|
241
|
+
def load_question(self):
|
242
|
+
self.clear_answer_buttons()
|
243
|
+
if self.controller.current_q_index >= len(self.controller.questions):
|
244
|
+
self.controller.end_game()
|
245
|
+
return
|
246
|
+
|
247
|
+
qdata = self.controller.questions[self.controller.current_q_index]
|
248
|
+
question = html.unescape(qdata["question"])
|
249
|
+
correct_answer = html.unescape(qdata["correct_answer"])
|
250
|
+
incorrect_answers = [html.unescape(ans) for ans in qdata["incorrect_answers"]]
|
251
|
+
options = incorrect_answers + [correct_answer]
|
252
|
+
random.shuffle(options)
|
253
|
+
|
254
|
+
self.correct_answer = correct_answer
|
255
|
+
self.current_category = qdata.get("category", "Unknown")
|
256
|
+
|
257
|
+
self.question_text.config(text=question)
|
258
|
+
|
259
|
+
# Create buttons in 2 columns
|
260
|
+
self.answer_buttons = []
|
261
|
+
for i, option in enumerate(options):
|
262
|
+
btn = RoundedButton(self.answers_frame, option, command=lambda opt=option: self.check_answer(opt), width=540, height=60)
|
263
|
+
self.answer_buttons.append(btn)
|
264
|
+
btn.grid(row=i//2, column=i%2, padx=20, pady=15)
|
265
|
+
|
266
|
+
self.update_status()
|
267
|
+
self.update_ai_prediction()
|
268
|
+
|
269
|
+
def clear_answer_buttons(self):
|
270
|
+
for btn in self.answer_buttons:
|
271
|
+
btn.destroy()
|
272
|
+
self.answer_buttons = []
|
273
|
+
|
274
|
+
def check_answer(self, choice):
|
275
|
+
if self.controller.paused:
|
276
|
+
return
|
277
|
+
|
278
|
+
correct = choice == self.correct_answer
|
279
|
+
if correct:
|
280
|
+
self.controller.score += 10
|
281
|
+
self.controller.streak += 1
|
282
|
+
if self.controller.streak > self.controller.max_streak:
|
283
|
+
self.controller.max_streak = self.controller.streak
|
284
|
+
else:
|
285
|
+
self.controller.streak = 0
|
286
|
+
|
287
|
+
# Update AI stats
|
288
|
+
self.controller.update_game_stats(correct, self.current_category)
|
289
|
+
|
290
|
+
# Flash question text color and disable buttons temporarily
|
291
|
+
self.flash_feedback(correct)
|
292
|
+
|
293
|
+
def flash_feedback(self, correct):
|
294
|
+
color = CORRECT_COLOR if correct else WRONG_COLOR
|
295
|
+
self.question_text.config(fg=color)
|
296
|
+
for btn in self.answer_buttons:
|
297
|
+
btn.unbind("<Button-1>")
|
298
|
+
self.after(1200, self.next_question)
|
299
|
+
|
300
|
+
def next_question(self):
|
301
|
+
self.question_text.config(fg=TEXT_COLOR)
|
302
|
+
self.controller.current_q_index += 1
|
303
|
+
self.load_question()
|
304
|
+
|
305
|
+
def update_status(self):
|
306
|
+
status = f"Score: {self.controller.score} Streak: {self.controller.streak} Max Streak: {self.controller.max_streak}"
|
307
|
+
self.status_label.config(text=status)
|
308
|
+
|
309
|
+
def update_ai_prediction(self):
|
310
|
+
stats = self.controller.ai_stats
|
311
|
+
answered = stats['questions_answered']
|
312
|
+
if answered == 0:
|
313
|
+
text = "AI Prediction: No data yet."
|
314
|
+
else:
|
315
|
+
acc = stats['correct_answers'] / answered
|
316
|
+
best_cat = max(stats['correct_by_category'], key=stats['correct_by_category'].get) if stats['correct_by_category'] else "N/A"
|
317
|
+
text = f"AI Prediction:\nAccuracy: {acc*100:.1f}%\nBest Category: {best_cat}"
|
318
|
+
self.ai_label.config(text=text)
|
319
|
+
|
320
|
+
def show_pause(self):
|
321
|
+
self.controller.paused = True
|
322
|
+
self.controller.show_frame("PauseOverlay")
|
323
|
+
|
324
|
+
|
325
|
+
class PauseOverlay(tk.Frame):
|
326
|
+
def __init__(self, parent, controller):
|
327
|
+
super().__init__(parent, bg="#000000") # semi-transparent black overlay
|
328
|
+
self.controller = controller
|
329
|
+
|
330
|
+
panel = tk.Frame(self, bg=PANEL_BG, width=400, height=300)
|
331
|
+
panel.place(relx=0.5, rely=0.5, anchor="center")
|
332
|
+
|
333
|
+
title = tk.Label(panel, text="Game Paused", font=FONT_LARGE, fg=TEXT_COLOR, bg=PANEL_BG)
|
334
|
+
title.pack(pady=(40, 30))
|
335
|
+
|
336
|
+
btn_resume = RoundedButton(panel, "Resume", command=self.resume_game)
|
337
|
+
btn_resume.pack(pady=15)
|
338
|
+
|
339
|
+
btn_menu = RoundedButton(panel, "Return to Menu", command=self.return_to_menu)
|
340
|
+
btn_menu.pack(pady=15)
|
341
|
+
|
342
|
+
def resume_game(self):
|
343
|
+
self.controller.paused = False
|
344
|
+
self.controller.show_frame("GameFrame")
|
345
|
+
|
346
|
+
def return_to_menu(self):
|
347
|
+
self.controller.paused = False
|
348
|
+
self.controller.show_frame("MenuFrame")
|
349
|
+
|
350
|
+
|
351
|
+
class StatsFrame(tk.Frame):
|
352
|
+
def __init__(self, parent, controller):
|
353
|
+
super().__init__(parent, bg=BG_COLOR)
|
354
|
+
self.controller = controller
|
355
|
+
|
356
|
+
title = tk.Label(self, text="Statistics", font=FONT_LARGE, fg=ACCENT_COLOR, bg=BG_COLOR)
|
357
|
+
title.pack(pady=(60, 40))
|
358
|
+
|
359
|
+
self.stats_text = tk.Label(self, text="", font=FONT_MEDIUM, fg=TEXT_COLOR, bg=BG_COLOR, justify="left")
|
360
|
+
self.stats_text.pack(pady=20)
|
361
|
+
|
362
|
+
btn_back = RoundedButton(self, "Back to Menu", command=lambda: controller.show_frame("MenuFrame"), width=300, height=60)
|
363
|
+
btn_back.pack(pady=40)
|
364
|
+
|
365
|
+
self.update_stats()
|
366
|
+
|
367
|
+
def update_stats(self):
|
368
|
+
s = self.controller.stats
|
369
|
+
games = s.get("games_played", 0)
|
370
|
+
avg_score = s.get("total_score", 0) / games if games > 0 else 0
|
371
|
+
max_score = s.get("max_score", 0)
|
372
|
+
max_streak = s.get("max_streak", 0)
|
373
|
+
stats_text = (
|
374
|
+
f"Games Played: {games}\n"
|
375
|
+
f"Average Score: {avg_score:.2f}\n"
|
376
|
+
f"Max Score: {max_score}\n"
|
377
|
+
f"Max Streak: {max_streak}\n"
|
378
|
+
)
|
379
|
+
self.stats_text.config(text=stats_text)
|
380
|
+
|
381
|
+
|
382
|
+
# ------------------------------
|
383
|
+
# Run if main
|
384
|
+
# ------------------------------
|
385
|
+
|
386
|
+
if __name__ == "__main__":
|
387
|
+
game = Game(difficulty="medium")
|
388
|
+
game.run()
|
@@ -0,0 +1,9 @@
|
|
1
|
+
setup.py
|
2
|
+
quastt_show/__init__.py
|
3
|
+
quastt_show/game.py
|
4
|
+
quastt_show.egg-info/PKG-INFO
|
5
|
+
quastt_show.egg-info/SOURCES.txt
|
6
|
+
quastt_show.egg-info/dependency_links.txt
|
7
|
+
quastt_show.egg-info/entry_points.txt
|
8
|
+
quastt_show.egg-info/requires.txt
|
9
|
+
quastt_show.egg-info/top_level.txt
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
requests
|
@@ -0,0 +1 @@
|
|
1
|
+
quastt_show
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from setuptools import setup, find_packages
|
2
|
+
|
3
|
+
setup(
|
4
|
+
name="quastt_show",
|
5
|
+
version="0.1.2",
|
6
|
+
description="A trivia game with AI stats.",
|
7
|
+
author="RVA",
|
8
|
+
packages=find_packages(),
|
9
|
+
install_requires=["requests"],
|
10
|
+
python_requires=">=3.7",
|
11
|
+
entry_points={
|
12
|
+
"console_scripts": [
|
13
|
+
"quastt-show=quastt_show.game:main",
|
14
|
+
],
|
15
|
+
},
|
16
|
+
include_package_data=True,
|
17
|
+
)
|