Загрузка данных


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()