Загрузка данных
import json
import random
import os
from tkinter import *
from tkinter import messagebox, simpledialog
from tkinter.ttk import Progressbar
DATA_FILE = "words_data.json"
DEFAULT_WORDS = {
"Приветствия": [
{"word": "Hello", "translation": "Привет", "correct": 0, "wrong": 0},
{"word": "Good morning", "translation": "Доброе утро", "correct": 0, "wrong": 0},
{"word": "Good night", "translation": "Спокойной ночи", "correct": 0, "wrong": 0},
{"word": "Goodbye", "translation": "До свидания", "correct": 0, "wrong": 0},
{"word": "See you later", "translation": "Увидимся позже", "correct": 0, "wrong": 0},
],
"Животные": [
{"word": "Cat", "translation": "Кот", "correct": 0, "wrong": 0},
{"word": "Dog", "translation": "Собака", "correct": 0, "wrong": 0},
{"word": "Elephant", "translation": "Слон", "correct": 0, "wrong": 0},
{"word": "Tiger", "translation": "Тигр", "correct": 0, "wrong": 0},
{"word": "Bird", "translation": "Птица", "correct": 0, "wrong": 0},
],
"Еда": [
{"word": "Apple", "translation": "Яблоко", "correct": 0, "wrong": 0},
{"word": "Bread", "translation": "Хлеб", "correct": 0, "wrong": 0},
{"word": "Water", "translation": "Вода", "correct": 0, "wrong": 0},
{"word": "Cheese", "translation": "Сыр", "correct": 0, "wrong": 0},
{"word": "Pizza", "translation": "Пицца", "correct": 0, "wrong": 0},
],
"Цвета": [
{"word": "Red", "translation": "Красный", "correct": 0, "wrong": 0},
{"word": "Blue", "translation": "Синий", "correct": 0, "wrong": 0},
{"word": "Green", "translation": "Зелёный", "correct": 0, "wrong": 0},
{"word": "Yellow", "translation": "Жёлтый", "correct": 0, "wrong": 0},
{"word": "Black", "translation": "Чёрный", "correct": 0, "wrong": 0},
]
}
class WordLearningApp:
def __init__(self, root):
self.root = root
self.root.title("WordMaster")
self.root.geometry("800x600")
self.root.configure(bg="#1e1e2e")
self.root.resizable(False, False)
self.words = self.load_data()
self.current_category = None
self.show_translation = False
self.card_words = []
self.card_index = 0
self.quiz_words = []
self.quiz_index = 0
self.quiz_score = 0
self.bg_color = "#1e1e2e"
self.card_bg = "#2d2d44"
self.accent = "#7aa2f7"
self.text_color = "#c0caf5"
self.success_color = "#9ece6a"
self.error_color = "#f7768e"
self.warning_color = "#e0af68"
self.setup_ui()
self.show_main_menu()
def load_data(self):
if os.path.exists(DATA_FILE):
with open(DATA_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return {k: [dict(w) for w in v] for k, v in DEFAULT_WORDS.items()}
def save_data(self):
with open(DATA_FILE, "w", encoding="utf-8") as f:
json.dump(self.words, f, ensure_ascii=False, indent=2)
def clear_frame(self):
for widget in self.root.winfo_children():
widget.destroy()
def setup_ui(self):
self.header = Frame(self.root, bg=self.bg_color, height=60)
self.header.pack(fill=X, padx=20, pady=10)
self.title_label = Label(self.header, text="WordMaster", font=("Helvetica", 24, "bold"), bg=self.bg_color, fg=self.accent)
self.title_label.pack(side=LEFT)
self.back_btn = Button(self.header, text="<-- Назад", font=("Helvetica", 12), bg=self.card_bg, fg=self.text_color, activebackground=self.accent, activeforeground="white", bd=0, padx=15, pady=5, cursor="hand2", command=self.show_main_menu)
self.main_frame = Frame(self.root, bg=self.bg_color)
self.main_frame.pack(fill=BOTH, expand=True, padx=20, pady=10)
def show_main_menu(self):
self.clear_frame()
self.setup_ui()
Label(self.main_frame, text="Выберите режим обучения", font=("Helvetica", 18), bg=self.bg_color, fg=self.text_color).pack(pady=20)
modes = [
("Карточки", "Изучай слова с переводом", self.show_categories_cards),
("Тест", "Проверь свой перевод", self.show_categories_quiz),
("Статистика", "Прогресс обучения", self.show_statistics),
("Словарь", "Управление словами", self.show_dictionary),
]
for title, desc, cmd in modes:
card = Frame(self.main_frame, bg=self.card_bg, highlightbackground=self.accent, highlightthickness=1, bd=0)
card.pack(fill=X, pady=8, padx=40)
card.bind("<Button-1>", lambda e, c=cmd: c())
card.config(cursor="hand2")
Label(card, text=title, font=("Helvetica", 16, "bold"), bg=self.card_bg, fg=self.accent).pack(anchor=W, padx=20, pady=(15, 5))
Label(card, text=desc, font=("Helvetica", 12), bg=self.card_bg, fg=self.text_color).pack(anchor=W, padx=20, pady=(0, 15))
def show_categories_cards(self):
self.show_category_selector(self.start_cards)
def show_categories_quiz(self):
self.show_category_selector(self.start_quiz)
def show_category_selector(self, callback):
self.clear_frame()
self.setup_ui()
self.back_btn.pack(side=RIGHT)
Label(self.main_frame, text="Выберите категорию", font=("Helvetica", 18), bg=self.bg_color, fg=self.text_color).pack(pady=20)
for cat_name in self.words.keys():
count = len(self.words[cat_name])
btn = Button(self.main_frame, text=f"{cat_name} ({count} слов)", font=("Helvetica", 14), bg=self.card_bg, fg=self.text_color, activebackground=self.accent, activeforeground="white", bd=0, padx=20, pady=12, cursor="hand2", command=lambda c=cat_name: callback(c))
btn.pack(fill=X, pady=5, padx=40)
btn = Button(self.main_frame, text="Все категории (случайно)", font=("Helvetica", 14), bg=self.warning_color, fg=self.bg_color, activebackground=self.accent, activeforeground="white", bd=0, padx=20, pady=12, cursor="hand2", command=lambda: callback("__all__"))
btn.pack(fill=X, pady=5, padx=40)
def start_cards(self, category):
self.current_category = category
if category == "__all__":
all_words = []
for cat in self.words.values():
all_words.extend(cat)
self.card_words = all_words.copy()
else:
self.card_words = self.words[category].copy()
random.shuffle(self.card_words)
self.card_index = 0
self.show_translation = False
self.show_card_screen()
def show_card_screen(self):
self.clear_frame()
self.setup_ui()
self.back_btn.pack(side=RIGHT)
if not self.card_words:
Label(self.main_frame, text="Нет слов в категории", font=("Helvetica", 16), bg=self.bg_color, fg=self.error_color).pack(pady=50)
return
word_data = self.card_words[self.card_index]
progress_text = f"{self.card_index + 1} / {len(self.card_words)}"
Label(self.main_frame, text=progress_text, font=("Helvetica", 12), bg=self.bg_color, fg=self.text_color).pack(pady=10)
card_frame = Frame(self.main_frame, bg=self.card_bg, highlightbackground=self.accent, highlightthickness=2, bd=0, width=500, height=300)
card_frame.pack(pady=20)
card_frame.pack_propagate(False)
Label(card_frame, text=word_data["word"], font=("Helvetica", 32, "bold"), bg=self.card_bg, fg=self.accent).pack(expand=True)
Label(card_frame, text="???" if not self.show_translation else word_data["translation"], font=("Helvetica", 24), bg=self.card_bg, fg=self.text_color if self.show_translation else "#555577").pack(pady=(0, 40))
btn_frame = Frame(self.main_frame, bg=self.bg_color)
btn_frame.pack(pady=20)
Button(btn_frame, text="Показать перевод" if not self.show_translation else "Скрыть перевод", font=("Helvetica", 14), bg=self.accent, fg="white", activebackground="#5d7bc7", bd=0, padx=25, pady=10, cursor="hand2", command=self.flip_card).pack(side=LEFT, padx=10)
Button(btn_frame, text="Знаю +", font=("Helvetica", 14), bg=self.success_color, fg=self.bg_color, activebackground="#7ab84a", bd=0, padx=20, pady=10, cursor="hand2", command=self.mark_known).pack(side=LEFT, padx=10)
Button(btn_frame, text="Учить -", font=("Helvetica", 14), bg=self.error_color, fg="white", activebackground="#d65d6e", bd=0, padx=20, pady=10, cursor="hand2", command=self.mark_unknown).pack(side=LEFT, padx=10)
nav_frame = Frame(self.main_frame, bg=self.bg_color)
nav_frame.pack(pady=10)
Button(nav_frame, text="<-- Предыдущее", font=("Helvetica", 12), bg=self.card_bg, fg=self.text_color, bd=0, padx=15, pady=5, cursor="hand2", command=self.prev_card).pack(side=LEFT, padx=5)
Button(nav_frame, text="Следующее -->", font=("Helvetica", 12), bg=self.card_bg, fg=self.text_color, bd=0, padx=15, pady=5, cursor="hand2", command=self.next_card).pack(side=LEFT, padx=5)
def flip_card(self):
self.show_translation = not self.show_translation
self.show_card_screen()
def mark_known(self):
word = self.card_words[self.card_index]
word["correct"] += 1
self.save_data()
self.next_card()
def mark_unknown(self):
word = self.card_words[self.card_index]
word["wrong"] += 1
self.save_data()
self.next_card()
def next_card(self):
self.show_translation = False
if self.card_index < len(self.card_words) - 1:
self.card_index += 1
self.show_card_screen()
else:
messagebox.showinfo("Готово!", "Вы просмотрели все слова в категории!")
self.show_main_menu()
def prev_card(self):
self.show_translation = False
if self.card_index > 0:
self.card_index -= 1
self.show_card_screen()
def start_quiz(self, category):
self.current_category = category
if category == "__all__":
all_words = []
for cat in self.words.values():
all_words.extend(cat)
self.quiz_words = all_words.copy()
else:
self.quiz_words = self.words[category].copy()
random.shuffle(self.quiz_words)
self.quiz_words = self.quiz_words[:10]
self.quiz_index = 0
self.quiz_score = 0
self.show_quiz_screen()
def show_quiz_screen(self):
self.clear_frame()
self.setup_ui()
self.back_btn.pack(side=RIGHT)
if self.quiz_index >= len(self.quiz_words):
self.show_quiz_results()
return
word_data = self.quiz_words[self.quiz_index]
progress = (self.quiz_index / len(self.quiz_words)) * 100
Label(self.main_frame, text=f"Вопрос {self.quiz_index + 1} из {len(self.quiz_words)}", font=("Helvetica", 14), bg=self.bg_color, fg=self.text_color).pack(pady=10)
progress_bar = Progressbar(self.main_frame, orient=HORIZONTAL, length=400, mode='determinate', value=progress)
progress_bar.pack(pady=5)
Label(self.main_frame, text="Переведите слово:", font=("Helvetica", 16), bg=self.bg_color, fg=self.text_color).pack(pady=20)
Label(self.main_frame, text=word_data["word"], font=("Helvetica", 36, "bold"), bg=self.bg_color, fg=self.accent).pack(pady=10)
self.answer_var = StringVar()
self.answer_entry = Entry(self.main_frame, textvariable=self.answer_var, font=("Helvetica", 18), bg=self.card_bg, fg="white", insertbackground="white", justify=CENTER, bd=0, highlightthickness=1, highlightcolor=self.accent, highlightbackground="#444466")
self.answer_entry.pack(pady=20, ipadx=10, ipady=5)
self.answer_entry.focus()
self.answer_entry.bind("<Return>", lambda e: self.check_answer())
Button(self.main_frame, text="Проверить", font=("Helvetica", 14), bg=self.accent, fg="white", activebackground="#5d7bc7", bd=0, padx=30, pady=10, cursor="hand2", command=self.check_answer).pack(pady=20)
self.hint_label = Label(self.main_frame, text="", font=("Helvetica", 14), bg=self.bg_color, fg=self.warning_color)
self.hint_label.pack(pady=10)
def check_answer(self):
user_answer = self.answer_var.get().strip().lower()
correct_answer = self.quiz_words[self.quiz_index]["translation"].lower()
if user_answer == correct_answer:
self.quiz_score += 1
self.quiz_words[self.quiz_index]["correct"] += 1
messagebox.showinfo("Правильно!", "Отличный перевод!")
else:
self.quiz_words[self.quiz_index]["wrong"] += 1
correct = self.quiz_words[self.quiz_index]["translation"]
messagebox.showinfo("Неправильно", f"Правильный ответ: {correct}")
self.save_data()
self.quiz_index += 1
self.show_quiz_screen()
def show_quiz_results(self):
self.clear_frame()
self.setup_ui()
self.back_btn.pack(side=RIGHT)
percent = (self.quiz_score / len(self.quiz_words)) * 100
Label(self.main_frame, text="Результаты теста", font=("Helvetica", 24, "bold"), bg=self.bg_color, fg=self.accent).pack(pady=30)
Label(self.main_frame, text=f"{self.quiz_score} / {len(self.quiz_words)}", font=("Helvetica", 48, "bold"), bg=self.bg_color, fg=self.success_color if percent >= 70 else self.error_color).pack(pady=20)
Label(self.main_frame, text=f"{percent:.0f}% правильных ответов", font=("Helvetica", 18), bg=self.bg_color, fg=self.text_color).pack(pady=10)
if percent >= 80:
grade = "Отлично!"
grade_color = self.success_color
elif percent >= 60:
grade = "Хорошо!"
grade_color = self.warning_color
else:
grade = "Продолжай учить!"
grade_color = self.error_color
Label(self.main_frame, text=grade, font=("Helvetica", 20, "bold"), bg=self.bg_color, fg=grade_color).pack(pady=20)
Button(self.main_frame, text="В главное меню", font=("Helvetica", 14), bg=self.accent, fg="white", bd=0, padx=30, pady=10, cursor="hand2", command=self.show_main_menu).pack(pady=30)
def show_statistics(self):
self.clear_frame()
self.setup_ui()
self.back_btn.pack(side=RIGHT)
Label(self.main_frame, text="Статистика обучения", font=("Helvetica", 22, "bold"), bg=self.bg_color, fg=self.accent).pack(pady=20)
total_words = 0
total_correct = 0
total_wrong = 0
for cat, words in self.words.items():
cat_frame = Frame(self.main_frame, bg=self.card_bg, bd=0)
cat_frame.pack(fill=X, pady=5, padx=40)
cat_correct = sum(w["correct"] for w in words)
cat_wrong = sum(w["wrong"] for w in words)
cat_total = len(words)
total_words += cat_total
total_correct += cat_correct
total_wrong += cat_wrong
attempts = cat_correct + cat_wrong
accuracy = (cat_correct / attempts * 100) if attempts > 0 else 0
Label(cat_frame, text=cat, font=("Helvetica", 14, "bold"), bg=self.card_bg, fg=self.accent).pack(anchor=W, padx=15, pady=(10, 0))
Label(cat_frame, text=f"Слов: {cat_total} | Правильно: {cat_correct} | Ошибок: {cat_wrong} | Точность: {accuracy:.0f}%", font=("Helvetica", 12), bg=self.card_bg, fg=self.text_color).pack(anchor=W, padx=15, pady=(0, 10))
total_attempts = total_correct + total_wrong
total_accuracy = (total_correct / total_attempts * 100) if total_attempts > 0 else 0
summary_frame = Frame(self.main_frame, bg=self.bg_color)
summary_frame.pack(pady=30)
Label(summary_frame, text=f"Всего слов: {total_words} | Общая точность: {total_accuracy:.0f}%", font=("Helvetica", 16, "bold"), bg=self.bg_color, fg=self.text_color).pack()
def show_dictionary(self):
self.clear_frame()
self.setup_ui()
self.back_btn.pack(side=RIGHT)
Label(self.main_frame, text="Управление словарём", font=("Helvetica", 22, "bold"), bg=self.bg_color, fg=self.accent).pack(pady=15)
btn_frame = Frame(self.main_frame, bg=self.bg_color)
btn_frame.pack(pady=10)
Button(btn_frame, text="Добавить категорию", font=("Helvetica", 12), bg=self.card_bg, fg=self.text_color, bd=0, padx=15, pady=8, cursor="hand2", command=self.add_category).pack(side=LEFT, padx=5)
Button(btn_frame, text="Добавить слово", font=("Helvetica", 12), bg=self.card_bg, fg=self.text_color, bd=0, padx=15, pady=8, cursor="hand2", command=self.add_word_dialog).pack(side=LEFT, padx=5)
Button(btn_frame, text="Удалить слово", font=("Helvetica", 12), bg=self.error_color, fg="white", bd=0, padx=15, pady=8, cursor="hand2", command=self.delete_word_dialog).pack(side=LEFT, padx=5)
canvas = Canvas(self.main_frame, bg=self.bg_color, highlightthickness=0)
scrollbar = Scrollbar(self.main_frame, orient="vertical", command=canvas.yview)
scroll_frame = Frame(canvas, bg=self.bg_color)
scroll_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.create_window((0, 0), window=scroll_frame, anchor="nw", width=760)
canvas.configure(yscrollcommand=scrollbar.set)
for cat_name, words in self.words.items():
cat_label = Label(scroll_frame, text=f"{cat_name} ({len(words)} слов)", font=("Helvetica", 14, "bold"), bg=self.bg_color, fg=self.accent)
cat_label.pack(anchor=W, pady=(15, 5), padx=20)
for word in words:
word_frame = Frame(scroll_frame, bg=self.card_bg)
word_frame.pack(fill=X, padx=40, pady=2)
Label(word_frame, text=f"{word['word']} — {word['translation']}", font=("Helvetica", 12), bg=self.card_bg, fg=self.text_color).pack(side=LEFT, padx=10, pady=5)
attempts = word["correct"] + word["wrong"]
if attempts > 0:
acc = word["correct"] / attempts * 100
Label(word_frame, text=f"Точность: {acc:.0f}%", font=("Helvetica", 10), bg=self.card_bg, fg=self.success_color if acc >= 70 else self.error_color).pack(side=RIGHT, padx=10, pady=5)
canvas.pack(side=LEFT, fill=BOTH, expand=True, pady=10)
scrollbar.pack(side=RIGHT, fill=Y)
def add_category(self):
name = simpledialog.askstring("Новая категория", "Введите название категории:")
if name and name.strip():
name = name.strip()
if name not in self.words:
self.words[name] = []
self.save_data()
self.show_dictionary()
else:
messagebox.showwarning("Ошибка", "Такая категория уже существует!")
def add_word_dialog(self):
if not self.words:
messagebox.showwarning("Ошибка", "Сначала создайте категорию!")
return
cat_window = Toplevel(self.root)
cat_window.title("Добавить слово")
cat_window.geometry("400x250")
cat_window.configure(bg=self.bg_color)
cat_window.transient(self.root)
cat_window.grab_set()
Label(cat_window, text="Категория:", bg=self.bg_color, fg=self.text_color, font=("Helvetica", 12)).pack(pady=5)
cat_var = StringVar(value=list(self.words.keys())[0])
cat_menu = OptionMenu(cat_window, cat_var, *self.words.keys())
cat_menu.config(bg=self.card_bg, fg=self.text_color, bd=0, highlightthickness=0)
cat_menu.pack(pady=5)
Label(cat_window, text="Слово (англ.):", bg=self.bg_color, fg=self.text_color, font=("Helvetica", 12)).pack(pady=5)
word_entry = Entry(cat_window, font=("Helvetica", 12), bg=self.card_bg, fg="white", insertbackground="white", bd=0)
word_entry.pack(pady=5)
Label(cat_window, text="Перевод:", bg=self.bg_color, fg=self.text_color, font=("Helvetica", 12)).pack(pady=5)
trans_entry = Entry(cat_window, font=("Helvetica", 12), bg=self.card_bg, fg="white", insertbackground="white", bd=0)
trans_entry.pack(pady=5)
def save_word():
w = word_entry.get().strip()
t = trans_entry.get().strip()
c = cat_var.get()
if w and t:
self.words[c].append({"word": w, "translation": t, "correct": 0, "wrong": 0})
self.save_data()
cat_window.destroy()
self.show_dictionary()
else:
messagebox.showwarning("Ошибка", "Заполните все поля!")
Button(cat_window, text="Сохранить", command=save_word, bg=self.accent, fg="white", bd=0, padx=20, pady=5).pack(pady=15)
def delete_word_dialog(self):
if not self.words:
return
del_window = Toplevel(self.root)
del_window.title("Удалить слово")
del_window.geometry("400x200")
del_window.configure(bg=self.bg_color)
del_window.transient(self.root)
del_window.grab_set()
Label(del_window, text="Категория:", bg=self.bg_color, fg=self.text_color, font=("Helvetica", 12)).pack(pady=5)
cat_var = StringVar(value=list(self.words.keys())[0])
cat_menu = OptionMenu(del_window, cat_var, *self.words.keys())
cat_menu.config(bg=self.card_bg, fg=self.text_color, bd=0, highlightthickness=0)
cat_menu.pack(pady=5)
Label(del_window, text="Слово для удаления:", bg=self.bg_color, fg=self.text_color, font=("Helvetica", 12)).pack(pady=5)
word_entry = Entry(del_window, font=("Helvetica", 12), bg=self.card_bg, fg="white", insertbackground="white", bd=0)
word_entry.pack(pady=5)
def do_delete():
c = cat_var.get()
w = word_entry.get().strip().lower()
original = [x for x in self.words[c] if x["word"].lower() == w]
if original:
self.words[c].remove(original[0])
self.save_data()
del_window.destroy()
self.show_dictionary()
else:
messagebox.showwarning("Ошибка", "Слово не найдено!")
Button(del_window, text="Удалить", command=do_delete, bg=self.error_color, fg="white", bd=0, padx=20, pady=5).pack(pady=15)
if __name__ == "__main__":
root = Tk()
app = WordLearningApp(root)
root.mainloop()