Разработка программного модуля информационной системы «Игра «Собачья академия» #3

Merged
Anymorexxx merged 16 commits from huinya-obnovi into master 2024-12-07 02:52:11 +03:00
13 changed files with 5512 additions and 234 deletions
Showing only changes of commit 5d34162267 - Show all commits

BIN
database/DogAcademy.db Normal file

Binary file not shown.

View file

@ -1,3 +1,4 @@
import logging
from sqlalchemy import func
from sqlalchemy.orm import joinedload
from database.db_session import get_session
@ -6,36 +7,50 @@ from sqlalchemy.exc import SQLAlchemyError
def get_user_by_id(user_id):
"""Получение данных пользователя по ID."""
"""Получение данных пользователя по ID с предварительной загрузкой связанных данных."""
session = get_session()
try:
user = session.query(Users).filter_by(user_id=user_id).first()
user = (
session.query(Users)
.options(joinedload(Users.game_sessions)) # Предзагрузка связанных игровых сессий
.filter_by(user_id=user_id)
.first()
)
return user
except SQLAlchemyError as e:
print(f"Ошибка при получении пользователя: {e}")
logging.error(f"Ошибка при получении пользователя: {e}")
return None
finally:
session.close()
def create_user(login, password, username):
"""Создание нового пользователя в базе данных."""
"""Регистрация нового пользователя."""
session = get_session()
# Проверяем, есть ли уже пользователь с таким логином
# Проверка, есть ли уже пользователь с таким логином
if session.query(Auth).filter_by(login=login).first():
return False, "Логин уже используется."
# Создаем новую запись в таблице Auth
# Создаём новую запись в таблице Auth
new_auth = Auth(login=login, password=password)
session.add(new_auth)
try:
session.commit() # Сохраняем изменения в таблице Auth
# Создаем новую запись в таблице Users, связывая с только что добавленным Auth
# Создаём новую запись в таблице Users, связываем с только что добавленным Auth
new_user = Users(user_id=new_auth.user_id, username=username)
session.add(new_user)
session.commit() # Сохраняем изменения в таблице Users
# Создаём новый игровой процесс для этого пользователя
new_game_session = GameSession(user_id=new_user.user_id, level=1) # Устанавливаем уровень по умолчанию
session.add(new_game_session)
session.commit() # Сохраняем данные в GameSession
print(f"Пользователь {username} успешно добавлен!")
return True, "Регистрация успешна."
except SQLAlchemyError as e:
session.rollback() # Откат изменений при ошибке
print(f"Ошибка при создании пользователя: {e}")
@ -43,28 +58,18 @@ def create_user(login, password, username):
finally:
session.close()
return True, "Регистрация успешна."
def check_user(login, password=None):
"""Проверка существования пользователя по логину и паролю (если передан)."""
session = get_session()
try:
print(f"Проверяем пользователя с логином: {login}")
# Фильтрация только по логину
query = session.query(Auth).filter_by(login=login)
# Если передан пароль, фильтруем и по паролю
if password:
query = query.filter_by(password=password)
user = query.first()
if user:
print(f"Пользователь найден: {user.user_id}")
return user.user_id # Возвращаем user_id пользователя
return user.user_id
else:
print("Пользователь не найден.")
return None
except SQLAlchemyError as e:
print(f"Ошибка при проверке пользователя: {e}")
@ -76,6 +81,8 @@ def save_progress(user_id, level, score, duration, health, hunger, sleepiness):
"""Сохранение игрового прогресса в базу данных."""
session = get_session()
try:
if not user_id:
raise ValueError("user_id не указан!")
session_data = GameSession(
user_id=user_id,
level=level,
@ -89,8 +96,10 @@ def save_progress(user_id, level, score, duration, health, hunger, sleepiness):
session.add(session_data)
session.commit()
except SQLAlchemyError as e:
print(f"Ошибка при сохранении прогресса: {e}")
logging.error(f"Ошибка при сохранении прогресса: {e}")
session.rollback()
except ValueError as e:
logging.error(e)
finally:
session.close()
@ -100,7 +109,7 @@ def get_user_progress(user_id):
try:
return session.query(GameSession).filter_by(user_id=user_id).all()
except Exception as e:
print(f"Ошибка при получении прогресса пользователя: {e}")
logging.error(f"Ошибка при получении прогресса пользователя: {e}")
return []
finally:
session.close()

View file

@ -1,3 +1,7 @@
import logging
from sqlalchemy.exc import SQLAlchemyError
from database.db_session import get_session
from database.models import Dogs
@ -47,9 +51,15 @@ DOG_CHARACTERS = {
}
def populate_dogs():
"""
Заполнение таблицы Dogs предустановленными данными.
"""
session = get_session()
try:
logging.info("Начинается заполнение таблицы Dogs.")
for breed, data in DOG_CHARACTERS.items():
existing_dog = session.query(Dogs).filter_by(breed=breed).first()
if not existing_dog:
dog = Dogs(
breed=breed,
characteristics=data['characteristics'],
@ -59,9 +69,26 @@ def populate_dogs():
)
session.add(dog)
session.commit()
print("Таблица Dogs успешно заполнена.")
except Exception as e:
logging.info("Таблица Dogs успешно заполнена.")
except SQLAlchemyError as e:
session.rollback()
print(f"Ошибка при заполнении Dogs: {e}")
logging.error(f"Ошибка при заполнении Dogs: {e}")
finally:
session.close()
def get_all_dogs():
"""
Получение списка всех пород собак из базы данных.
:return: Список объектов Dogs.
"""
session = get_session()
try:
dogs = session.query(Dogs).all()
return dogs
except SQLAlchemyError as e:
logging.error(f"Ошибка при получении списка собак: {e}")
return []
finally:
session.close()

View file

@ -1,34 +1,48 @@
import logging
from sqlalchemy import func
from database.db_events import get_user_progress
from database.db_session import get_session
from sqlalchemy.exc import SQLAlchemyError
from database.models import GameSession
def save_game_session(user_id, level, score, duration, health, hunger, sleepiness):
"""Сохранение игрового процесса в таблицу GameSessions."""
def save_game_session(user_id, level, score, steps, duration=0, health=100, hunger=0, sleepiness=0):
"""Сохранение игрового прогресса."""
session = get_session()
try:
# Создаем новый объект GameSession
game_session = GameSession(
session.add(GameSession(
user_id=user_id,
level=level,
score=score,
steps=steps,
duration=duration,
health=health,
hunger=hunger,
sleepiness=sleepiness,
)
session.add(game_session)
session.commit() # Сохраняем данные в таблице
print(f"Игровой процесс для пользователя {user_id} на уровне {level} успешно сохранен.")
except SQLAlchemyError as e:
sleepiness=sleepiness
))
session.commit()
logging.info(f"Сессия сохранена: user_id={user_id}, level={level}, score={score}")
except Exception as e:
session.rollback()
print(f"Ошибка при сохранении игрового процесса: {e}")
finally:
session.close()
logging.error(f"Ошибка при сохранении игровой сессии: {e}")
raise
def print_user_progress(user_id):
"""Печать прогресса пользователя из таблицы GameSessions."""
"""
Печать прогресса пользователя из таблицы GameSessions.
:param user_id: ID пользователя
"""
if not user_id:
logging.error("user_id отсутствует. Невозможно получить прогресс.")
return
progress = get_user_progress(user_id)
if not progress:
print(f"У пользователя с ID {user_id} нет сохраненного прогресса.")
return
print(f"Прогресс пользователя (user_id={user_id}):")
for session in progress:
print(f"Уровень: {session.level}, Очки: {session.score}, Время: {session.duration} секунд")
print(f"- Уровень: {session.level}, Очки: {session.score}, Время: {session.duration} сек")

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,5 @@
import logging
from sqlalchemy.exc import SQLAlchemyError
from database.db_session import get_session
from database.models import Auth, Users, GameSession
@ -6,50 +8,42 @@ from database.models import Auth, Users, GameSession
def register_user(login, password, username):
"""Регистрация нового пользователя."""
session = get_session()
# Проверяем, есть ли уже пользователь с таким логином
try:
if session.query(Auth).filter_by(login=login).first():
return False, "Логин уже используется."
# Создаем новую запись в таблице Auth
# Создаем новую запись в Auth
new_auth = Auth(login=login, password=password)
session.add(new_auth)
session.commit()
try:
session.commit() # Сохраняем изменения в таблице Auth
# Создаем новую запись в таблице Users, связывая с только что добавленным Auth
# Используем new_auth.user_id для связи
# Создаем запись в Users
new_user = Users(user_id=new_auth.user_id, username=username)
session.add(new_user)
session.commit() # Сохраняем изменения в таблице Users
# Создаем новый игровой процесс в GameSession для этого пользователя
new_game_session = GameSession(user_id=new_user.user_id, level=1) # Устанавливаем уровень по умолчанию
# Создаем запись в GameSession
new_game_session = GameSession(user_id=new_user.user_id, level=1)
session.add(new_game_session)
session.commit() # Сохраняем данные в GameSession
session.commit()
print(f"Пользователь {username} успешно добавлен!")
return True, "Регистрация успешна."
except SQLAlchemyError as e:
session.rollback() # Откат изменений при ошибке
print(f"Ошибка при создании пользователя: {e}")
session.rollback()
logging.error(f"Ошибка при регистрации: {e}")
return False, "Произошла ошибка при регистрации."
finally:
session.close()
return True, "Регистрация успешна."
def login_user(login, password):
"""Авторизация пользователя."""
session = get_session()
# Проверяем, существует ли пользователь с таким логином и паролем
user_auth = session.query(Auth).filter_by(login=login, password=password).first()
if user_auth:
# Возвращаем успешный вход и ID пользователя из таблицы Users
user = session.query(Users).filter_by(user_id=user_auth.user_id).first()
return True, user.user_id
try:
auth = session.query(Auth).filter_by(login=login, password=password).first()
if auth:
return True, auth.user_id
return False, "Неверный логин или пароль."
except SQLAlchemyError as e:
return False, f"Ошибка авторизации: {e}"
finally:
session.close()

View file

@ -1,13 +1,17 @@
import logging
import tkinter as tk
from tkinter import messagebox
from config import BACKGROUND_COLOR, PRIMARY_COLOR, BUTTON_COLOR, BUTTON_TEXT_COLOR, FONT, BIG_FONT, ADMIN_LOGIN, ADMIN_PASSWORD
from src.auth import login_user
from src.ui.admin_ui import AdminApp # Импорт интерфейса администратора
from database.db_events import create_user, check_user
from src.ui.user_ui.main_menu import UserApp
class DogAcademyApp:
def __init__(self, root):
def __init__(self, root, user_id=None):
"""Инициализация приложения."""
self.root = root
self.user_id = user_id
self.root.title("Dog Academy Game")
self.root.geometry("1920x1080")
self.root.configure(bg=BACKGROUND_COLOR)
@ -20,12 +24,11 @@ class DogAcademyApp:
self.current_frame.destroy()
def show_main_menu(self):
"""Показать главное меню с названием игры и кнопками."""
"""Показать главное меню."""
self.clear_frame()
self.current_frame = tk.Frame(self.root, bg=BACKGROUND_COLOR)
self.current_frame.pack(expand=True)
# Название игры
title = tk.Label(
self.current_frame,
text="Dog Academy Game",
@ -35,7 +38,6 @@ class DogAcademyApp:
)
title.pack(pady=50)
# Кнопка "Войти"
login_button = tk.Button(
self.current_frame,
text="Войти",
@ -46,7 +48,6 @@ class DogAcademyApp:
)
login_button.pack(pady=20)
# Кнопка "Зарегистрироваться"
register_button = tk.Button(
self.current_frame,
text="Зарегистрироваться",
@ -63,7 +64,6 @@ class DogAcademyApp:
self.current_frame = tk.Frame(self.root, bg=BACKGROUND_COLOR)
self.current_frame.pack(expand=True)
# Заголовок
title = tk.Label(
self.current_frame,
text="Авторизация",
@ -73,19 +73,16 @@ class DogAcademyApp:
)
title.pack(pady=50)
# Логин
login_label = tk.Label(self.current_frame, text="Логин:", bg=BACKGROUND_COLOR, fg=PRIMARY_COLOR, font=FONT)
login_label.pack()
self.login_entry = tk.Entry(self.current_frame, font=FONT)
self.login_entry.pack(pady=10)
# Пароль
password_label = tk.Label(self.current_frame, text="Пароль:", bg=BACKGROUND_COLOR, fg=PRIMARY_COLOR, font=FONT)
password_label.pack()
self.password_entry = tk.Entry(self.current_frame, show="*", font=FONT)
self.password_entry.pack(pady=10)
# Кнопка "Показать пароль"
show_password_button = tk.Button(
self.current_frame,
text="Показать пароль",
@ -96,7 +93,6 @@ class DogAcademyApp:
)
show_password_button.pack(pady=10)
# Кнопка "Войти"
login_button = tk.Button(
self.current_frame,
text="Войти",
@ -107,7 +103,6 @@ class DogAcademyApp:
)
login_button.pack(pady=20)
# Кнопка "Вернуться на главную"
back_button = tk.Button(
self.current_frame,
text="Вернуться на главную",
@ -132,17 +127,21 @@ class DogAcademyApp:
if login == ADMIN_LOGIN and password == ADMIN_PASSWORD:
messagebox.showinfo("Успех", "Вы успешно авторизованы как администратор!")
self.show_admin_panel() # Переходим к админ-панели
elif check_user(login, password):
messagebox.showinfo("Успех", "Вы успешно авторизованы!")
self.show_user_dashboard() # Переходим к панели пользователя
self.user_id = None # Администратору не нужен user_id
self.show_admin_panel()
else:
messagebox.showerror("Ошибка", "Неверные данные. Попробуйте снова.")
success, user_id = login_user(login, password)
if success:
messagebox.showinfo("Успех", "Вы успешно авторизованы!")
self.user_id = user_id # Сохраняем user_id
self.show_user_dashboard()
else:
messagebox.showerror("Ошибка", "Неверный логин или пароль.")
def show_admin_panel(self):
"""Отображение интерфейса администратора."""
self.clear_frame()
AdminApp(self.root) # Создаем экземпляр админ-панели
AdminApp(self.root)
def show_registration_screen(self):
"""Показать экран регистрации."""
@ -150,7 +149,6 @@ class DogAcademyApp:
self.current_frame = tk.Frame(self.root, bg=BACKGROUND_COLOR)
self.current_frame.pack(expand=True)
# Заголовок
title = tk.Label(
self.current_frame,
text="Регистрация",
@ -160,36 +158,21 @@ class DogAcademyApp:
)
title.pack(pady=50)
# Логин
login_label = tk.Label(self.current_frame, text="Логин:", bg=BACKGROUND_COLOR, fg=PRIMARY_COLOR, font=FONT)
login_label.pack()
self.reg_login_entry = tk.Entry(self.current_frame, font=FONT)
self.reg_login_entry.pack(pady=10)
# Пароль
password_label = tk.Label(self.current_frame, text="Пароль:", bg=BACKGROUND_COLOR, fg=PRIMARY_COLOR, font=FONT)
password_label.pack()
self.reg_password_entry = tk.Entry(self.current_frame, show="*", font=FONT)
self.reg_password_entry.pack(pady=10)
# Кнопка "Показать пароль"
show_password_button = tk.Button(
self.current_frame,
text="Показать пароль",
bg=BUTTON_COLOR,
fg=BUTTON_TEXT_COLOR,
font=FONT,
command=self.toggle_registration_password,
)
show_password_button.pack(pady=10)
# Никнейм
username_label = tk.Label(self.current_frame, text="Никнейм:", bg=BACKGROUND_COLOR, fg=PRIMARY_COLOR, font=FONT)
username_label.pack()
self.username_entry = tk.Entry(self.current_frame, font=FONT)
self.username_entry.pack(pady=10)
# Кнопка "Зарегистрироваться"
register_button = tk.Button(
self.current_frame,
text="Зарегистрироваться",
@ -200,7 +183,6 @@ class DogAcademyApp:
)
register_button.pack(pady=20)
# Кнопка "Вернуться на главную"
back_button = tk.Button(
self.current_frame,
text="Вернуться на главную",
@ -222,19 +204,19 @@ class DogAcademyApp:
"""Регистрация нового пользователя."""
login = self.reg_login_entry.get()
password = self.reg_password_entry.get()
username = self.username_entry.get() # Получаем имя пользователя
username = self.username_entry.get()
if login and password and username:
success, message = create_user(login, password, username) # Передаем имя пользователя
success, message = create_user(login, password, username)
if success:
messagebox.showinfo("Успех", message)
self.show_login_screen()
else:
messagebox.showerror("Ошибка", message)
else:
messagebox.showerror("Ошибка", "Пожалуйста, заполните все поля.")
messagebox.showerror("Ошибка", "Заполните все поля.")
def show_user_dashboard(self):
"""Переход к пользовательскому интерфейсу."""
self.clear_frame()
"""Перейти к главному меню пользователя после авторизации."""
UserApp(self.root)
UserApp(self.root, self.user_id)

View file

@ -1,9 +1,10 @@
import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk
import random
import logging
from database.db_events import get_user_by_id
from database.db_events import get_user_by_id, get_user_progress, save_progress
from database.info.GameSessions_table import save_game_session
from src.user_functions.game_logs import setup_logging
from config import DOG_CHARACTERS, DONE, BONE, BACKGROUND_GAME
@ -12,14 +13,22 @@ from src.utils import clear_frame
# Настройка логирования
setup_logging()
user = get_user_by_id(user_id=1)
if user:
print(f"Данные пользователя: {user}")
else:
print("Пользователь не найден")
class GameUI:
def __init__(self, root, user_id, return_to_main_menu_callback):
if not user_id:
raise ValueError("user_id отсутствует при инициализации GameUI!")
self.root = root
self.user_id = user_id
self.return_to_main_menu_callback = return_to_main_menu_callback
self.selected_dog = None
self.current_level = 1
self.max_unlocked_level = 1
self.completed_levels = set()
self.total_bones = 0
@ -28,6 +37,11 @@ class GameUI:
self.bones_positions = []
self.max_bones_per_level = 10
self.steps_taken = 0
self.user_data = get_user_by_id(self.user_id)
# Получаем прогресс пользователя
self.user_progress = get_user_progress(user_id)
self.max_unlocked_level = max([session.level for session in self.user_progress]) if self.user_progress else 1
# Изображения
self.done_image = ImageTk.PhotoImage(Image.open(DONE).resize((50, 50), Image.Resampling.LANCZOS))
@ -45,7 +59,7 @@ class GameUI:
# Флаги
self.is_pause_menu_open = False
self.is_victory_screen_open = False
self.is_game_active = False # Для контроля состояния игры
self.is_game_active = False
# Привязка клавиш
self.root.bind("<KeyPress-w>", self.move_up)
@ -57,6 +71,15 @@ class GameUI:
# Отображение начального экрана
self.show_dog_selection()
# Инициализация интерфейса
self.create_main_menu_button()
if self.user_data:
self.max_unlocked_level = self.user_data.level or 1
self.total_bones = sum([session.score for session in get_user_progress(self.user_id)])
else:
logging.warning("Данные пользователя не найдены")
def create_background(self):
"""Создаёт фон для игры."""
try:
@ -122,62 +145,99 @@ class GameUI:
clear_frame(self.root)
self.create_background()
tk.Label(
self.root, text="Выберите уровень", font=("Comic Sans MS", 24), bg="#E5E5E5"
).pack(pady=20)
tk.Label(self.root, text="Выберите уровень", font=("Comic Sans MS", 24), bg="#E5E5E5").pack(pady=20)
level_frame = tk.Frame(self.root, bg="#E5E5E5")
level_frame.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
for level in range(1, 6):
progress = get_user_progress(self.user_id)
completed_levels = {session.level for session in progress if session.score > 0}
self.max_unlocked_level = max(completed_levels) if completed_levels else 1
for level in range(1, 101):
color = (
"#4CAF50" if level in completed_levels else
"#FFEB3B" if level <= self.max_unlocked_level else
"#A9A9A9"
)
state = tk.NORMAL if level <= self.max_unlocked_level else tk.DISABLED
button = tk.Button(
level_frame,
text=f"Уровень {level}",
state=tk.NORMAL if level <= self.max_unlocked_level else tk.DISABLED,
font=("Comic Sans MS", 20),
bg="#4CAF50" if level <= self.max_unlocked_level else "#A9A9A9",
width=15,
height=2,
command=lambda l=level: self.start_level(l),
bg=color,
state=state,
font=("Comic Sans MS", 14),
command=lambda l=level: self.handle_level_selection(l)
)
button.pack(pady=10)
button.grid(row=(level - 1) // 10, column=(level - 1) % 10, padx=5, pady=5)
tk.Button(
self.root,
text="Вернуться",
font=("Comic Sans MS", 16),
bg="lightgreen",
command=self.show_dog_selection,
command=self.show_dog_selection
).place(relx=0.5, rely=0.9, anchor=tk.CENTER)
def start_level(self, level):
"""Начало выбранного уровня."""
def handle_level_selection(self, level):
"""Обработка выбора уровня."""
if level in self.completed_levels:
if messagebox.askyesno("Повторить уровень", f"Вы уже прошли уровень {level}. Хотите пройти его заново?"):
self.current_level = level
self.total_bones = 0
self.steps_taken = 0
self.start_game()
elif level <= self.max_unlocked_level:
self.current_level = level
self.countdown()
else:
messagebox.showinfo("Недоступно", "Пройдите предыдущие уровни, чтобы разблокировать этот.")
def start_level(self, level):
"""Запуск уровня."""
if level in self.completed_levels:
if messagebox.askyesno("Повторить уровень", f"Вы уже прошли уровень {level}. Хотите пройти его заново?"):
self.current_level = level
self.total_bones = 0
self.steps_taken = 0
self.start_game() # Запускаем уровень заново
return
elif level <= self.max_unlocked_level:
self.current_level = level
self.countdown() # Запуск обратного отсчёта перед началом игры
else:
messagebox.showinfo("Недоступно", "Этот уровень заблокирован.")
def countdown(self):
"""Обратный отсчёт перед началом уровня."""
clear_frame(self.root)
"""Обратный отсчёт перед началом уровня с анимацией."""
clear_frame(self.root) # Очищаем экран
countdown_label = tk.Label(
self.root, text="", font=("Comic Sans MS", 30), bg="#E5E5E5"
self.root, text="Готовьтесь!", font=("Comic Sans MS", 40), bg="#E5E5E5"
)
countdown_label.pack(expand=True)
for i in range(3, 0, -1):
countdown_label.config(text=f"{i}...")
def update_countdown(counter):
if counter > 0:
countdown_label.config(text=f"{counter}", fg=random.choice(["red", "green", "blue"]))
self.root.update()
self.root.after(1000)
self.root.after(1000, update_countdown, counter - 1)
else:
countdown_label.config(text="Вперёд!", fg="orange")
self.root.update()
self.root.after(1000, self.start_game)
self.start_game()
update_countdown(3) # Старт обратного отсчёта с 3 секунд
def start_game(self):
"""Запуск игрового процесса."""
clear_frame(self.root)
logging.info(f"Игра начата на уровне {self.current_level}")
clear_frame(self.root) # Очищаем экран
self.map_canvas = tk.Canvas(self.root, width=1920, height=1080, bg="#E5E5E5")
self.map_canvas.pack()
self.draw_grid()
self.bones_positions = self.generate_bones()
self.bones_positions = self.generate_bones() # Генерация новых косточек
# Прямоугольник и изображение косточек (создаются один раз)
self.rect_x1, self.rect_y1 = 1600, 0
@ -189,11 +249,6 @@ class GameUI:
self.bones_label = tk.Label(self.root, text=f"{self.total_bones}", font=("Comic Sans MS", 16), bg="#CCCCCC")
self.bones_label.place(x=1700, y=30)
# В методе update_map (не удаляем прямоугольник):
self.map_canvas.delete("all") # Удаляем только динамичные объекты карты (косточки, собаку)
self.draw_grid()
self.collect_bones()
self.update_map() # Начальное обновление карты
def draw_grid(self):
@ -204,11 +259,15 @@ class GameUI:
self.map_canvas.create_line(0, y, 1920, y, fill="lightgray")
def generate_bones(self):
"""Генерация косточек на карте."""
return [
"""Генерация косточек на карте с увеличением их количества по геометрической прогрессии."""
# Количество косточек увеличивается по геометрической прогрессии
bones_count = min(5, 10 * (2 ** (self.current_level - 1))) # Ограничение в 5 косточек на уровне
bones = [
(random.randint(0, self.cols - 1), random.randint(0, self.rows - 1))
for _ in range(2)
for _ in range(bones_count)
]
logging.info(f"Генерация косточек: {bones}") # Логируем координаты косточек
return bones
def collect_bones(self):
"""Проверка и сбор косточек."""
@ -216,10 +275,19 @@ class GameUI:
if self.dog_position == [bone[0], bone[1]]:
self.bones_positions.remove(bone)
self.total_bones += 1
# Сохранение прогресса
save_progress(self.user_id, self.current_level, self.total_bones, self.steps_taken, 100, 0, 0)
self.bones_label.config(text=f"{self.total_bones}")
# Условие для победы
target_bones = 10 * (2 ** (self.current_level - 1)) # Модифицируем целевое количество косточек
if self.total_bones >= target_bones and not self.is_victory_screen_open:
self.show_victory_screen() # Показываем экран победы
if self.steps_taken % 10 == 0 and len(self.bones_positions) < self.max_bones_per_level:
self.bones_positions.extend(self.generate_bones())
self.bones_positions.extend(self.generate_bones()) # Генерация новых косточек, если нужно
def move_up(self, event):
"""Движение вверх."""
@ -299,6 +367,7 @@ class GameUI:
if self.is_pause_menu_open:
self.pause_window.destroy() # Закрываем окно паузы
self.is_pause_menu_open = False # Сбрасываем флаг
self.is_game_active = True # Возвращаем игру в активное состояние
def save_and_exit(self):
"""Сохранение данных и выход в главное меню."""
@ -313,8 +382,8 @@ class GameUI:
if self.is_victory_screen_open: # Отключаем обновления, если окно победы открыто
return
self.map_canvas.delete("all")
self.draw_grid()
self.map_canvas.delete("all") # Удаляем старые объекты карты
self.draw_grid() # Перерисовываем сетку
# Отображение косточек
for x, y in self.bones_positions:
@ -325,8 +394,11 @@ class GameUI:
)
# Отображение собаки
dog_image = Image.open(DOG_CHARACTERS[self.selected_dog]["image"]).resize((self.grid_size, self.grid_size),
Image.Resampling.LANCZOS)
if self.selected_dog:
dog_image = Image.open(DOG_CHARACTERS[self.selected_dog]["image"]).resize(
(self.grid_size, self.grid_size),
Image.Resampling.LANCZOS
)
self.dog_photo = ImageTk.PhotoImage(dog_image)
self.map_canvas.create_image(
self.dog_position[0] * self.grid_size + self.grid_size // 2,
@ -344,17 +416,17 @@ class GameUI:
def show_victory_screen(self):
"""Экран победы."""
if self.is_victory_screen_open: # Проверяем, чтобы не было второго окна
if self.is_victory_screen_open:
return
self.is_victory_screen_open = True # Устанавливаем флаг
self.is_game_active = False # Останавливаем движение
self.is_victory_screen_open = True
self.is_game_active = False
victory_window = tk.Toplevel(self.root)
victory_window.title("Ура, победа!")
victory_window.geometry("800x600")
victory_window.configure(bg="#E5E5E5")
victory_window.grab_set() # Блокируем взаимодействие с основным окном
victory_window.grab_set()
# Изображение собаки
dog_image = Image.open(DOG_CHARACTERS[self.selected_dog]["image"]).resize((200, 200), Image.Resampling.LANCZOS)
@ -364,26 +436,26 @@ class GameUI:
dog_label.place(x=50, y=50)
# Текст победы
victory_label = tk.Label(
victory_window, text="Ура, победа!", font=("Comic Sans MS", 24), bg="#E5E5E5"
)
victory_label = tk.Label(victory_window, text="Ура, победа!", font=("Comic Sans MS", 24), bg="#E5E5E5")
victory_label.pack(pady=20)
# Характеристики собаки
dog_info = f"Порода: {self.selected_dog}"
info_label = tk.Label(
victory_window, text=dog_info, font=("Comic Sans MS", 16), bg="#E5E5E5"
)
info_label = tk.Label(victory_window, text=dog_info, font=("Comic Sans MS", 16), bg="#E5E5E5")
info_label.place(x=300, y=100)
# Собрано косточек
target_bones = 10 * (2 ** (self.current_level - 1)) # Геометрическая прогрессия для косточек
target_bones = 10 * (2 ** (self.current_level - 1)) # Геометрическая прогрессия
collected_info = f"Собрано: {self.total_bones} из {target_bones}"
score_label = tk.Label(
victory_window, text=collected_info, font=("Comic Sans MS", 16), bg="#E5E5E5"
)
score_label = tk.Label(victory_window, text=collected_info, font=("Comic Sans MS", 16), bg="#E5E5E5")
score_label.place(x=300, y=150)
# Никнейм игрока
user_info = get_user_by_id(self.user_id)
username_label = tk.Label(victory_window, text=f"Никнейм: {user_info.username}", font=("Comic Sans MS", 16),
bg="#E5E5E5")
username_label.place(x=300, y=200)
# Кнопка перехода на следующий уровень
next_level_button = tk.Button(
victory_window,
@ -404,29 +476,91 @@ class GameUI:
)
exit_button.place(relx=0.5, rely=0.8, anchor=tk.CENTER)
def close_victory_window(self):
"""Закрытие окна победы и сброс флага."""
self.is_victory_screen_open = False
self.is_game_active = True
self.return_to_main_menu() # Возвращаем в меню
def return_to_main_menu(self):
"""Возврат в главное меню."""
self.is_victory_screen_open = False # Сбрасываем флаг окна победы
self.return_to_main_menu_callback() # Вызываем колбэк для возврата
if self.is_pause_menu_open:
self.pause_window.destroy() # Закрываем окно паузы
self.is_pause_menu_open = False # Сбрасываем флаг
clear_frame(self.root) # Очищаем текущий экран
self.show_main_menu() # Переходим в главное меню
def start_next_level(self):
"""Переход на следующий уровень."""
self.save_progress() # Сохраняем прогресс перед переходом на следующий уровень
try:
# Сохранение текущего прогресса
self.save_progress()
# Переход на следующий уровень
self.current_level += 1
self.total_bones = 0 # Сбрасываем счётчик косточек
self.start_level(self.current_level)
self.max_unlocked_level = max(self.max_unlocked_level, self.current_level)
# Сброс состояния игры: собака возвращается в начальную позицию
self.total_bones = 0
self.steps_taken = 0
self.dog_position = [1, 1] # Начальная позиция собаки
self.bones_positions = [] # Очищаем текущие косточки
# Генерация новых косточек с увеличением их количества по геометрической прогрессии
self.bones_positions = self.generate_bones()
# Начинаем новый уровень с обратным отсчётом
self.countdown() # Запуск обратного отсчёта
except Exception as e:
logging.error(f"Ошибка при переходе на следующий уровень: {e}")
def save_progress(self):
"""Сохранение игрового процесса в таблицу GameSessions."""
from datetime import datetime
if not self.user_id:
logging.error("Ошибка: user_id равен None. Запись невозможна.")
return
# Получаем время начала и окончания уровня
duration = self.steps_taken # Время можно рассчитать по шагам или в реальном времени
score = self.total_bones # Количество собранных косточек
try:
# Рассчитываем длительность уровня и текущий счет
duration = self.steps_taken
score = self.total_bones
# Сохранение прогресса в базу данных
save_game_session(self.user_id, self.current_level, score, duration, 100, 0, 0)
# Логирование
logging.info(
f"Сохранение сессии: user_id={self.user_id}, level={self.current_level}, score={score}, duration={duration}")
# Сохранение данных в базу
save_game_session(
user_id=self.user_id,
level=self.current_level,
score=self.total_bones,
steps=self.steps_taken, # Используйте количество шагов или время
duration=self.steps_taken, # Если 'steps' отражают время, используйте их
health=100, # Примерное значение
hunger=0, # Примерное значение
sleepiness=0 # Примерное значение
)
logging.info("Прогресс успешно сохранен.")
except Exception as e:
logging.error(f"Ошибка при сохранении прогресса: {e}")
raise
def create_main_menu_button(self):
"""Создаём кнопку для возврата в главное меню."""
main_menu_button = tk.Button(
self.root,
text="Главное меню",
font=("Comic Sans MS", 16),
bg="lightgreen",
command=self.show_main_menu, # Вызов нового метода
)
main_menu_button.pack()
def show_main_menu(self):
"""Переход в главное меню."""
self.is_game_active = False # Останавливаем игру, если мы возвращаемся в меню
clear_frame(self.root) # Очищаем текущий экран
self.return_to_main_menu_callback() # Вызов колбэка для возврата в главное меню
# Также можно сохранять дополнительные параметры, если необходимо (например, здоровье, голод, усталость)

View file

@ -1,6 +1,7 @@
import os
import logging
import tkinter as tk
from functools import partial
from tkinter import messagebox, Canvas
from PIL import Image, ImageTk
import math
@ -31,10 +32,11 @@ PLAY_BUTTON_RADIUS = 100 # Радиус кнопки "Играть"
class UserApp:
def __init__(self, root, user_id=None):
def __init__(self, root, user_id):
"""Инициализация пользовательского интерфейса."""
self.root = root
self.user_id = user_id
self.root.configure(bg=BACKGROUND_COLOR)
self.root.configure(bg="#E5E5E5")
self.root.geometry("1920x1080")
self.root.title("Собачья академия")
self.show_user_dashboard()
@ -151,8 +153,12 @@ class UserApp:
def show_profile(self):
"""Показать экран профиля пользователя."""
try:
self.clear_frame()
profile_ui(self.root, self.user_id, self) # Передаем сам объект self для доступа к show_user_dashboard
profile_ui(self.root, self.user_id, self)
except Exception as e:
logging.error(f"Ошибка при отображении профиля: {e}")
messagebox.showerror("Ошибка", "Не удалось открыть профиль.")
def clear_frame(self):
"""Очистить текущий экран."""
@ -160,14 +166,15 @@ class UserApp:
widget.destroy()
def play_game(self):
"""Переход к игровому интерфейсу."""
print("Запуск игры...")
"""Запуск игры и передача колбэка для возврата в меню."""
# Передаем метод через partial для корректной передачи self
return_to_main_menu = partial(self.return_to_main_menu)
GameUI(self.root, self.user_id, return_to_main_menu)
def return_to_main_menu():
clear_frame(self.root)
self.show_user_dashboard() # Возврат в главное меню
GameUI(self.root, self.user_id, return_to_main_menu) # Передаём колбэк для возврата
def return_to_main_menu(self):
"""Возврат в главное меню."""
self.clear_frame() # Очищаем экран перед переходом
self.show_user_dashboard() # Показываем главное меню
def exit_app(self):
"""Подтверждение выхода из приложения."""

View file

@ -1,6 +1,6 @@
import tkinter as tk
from src.utils import clear_frame
from database.db_events import get_user_progress
from database.db_events import get_user_progress, get_user_by_id
def profile_ui(root, user_id, user_app):
@ -10,12 +10,24 @@ def profile_ui(root, user_id, user_app):
frame = tk.Frame(root, bg="#f8e1e1")
frame.pack(fill=tk.BOTH, expand=True)
tk.Label(frame, text="Профиль", font=("Comic Sans MS", 30), bg="#f8e1e1").pack(pady=20)
# Получение прогресса пользователя из базы данных
# Обновляем данные пользователя из базы
user = get_user_by_id(user_id)
if not user:
username = "Неизвестный пользователь"
levels_completed = 0
bones_collected = 0
else:
username = user.username
progress = get_user_progress(user_id)
levels_completed = len(progress) # Считаем количество уровней
bones_collected = sum([session.score for session in progress]) # Суммируем все собранные косточки
levels_completed = len({session.level for session in progress if session.score > 0})
bones_collected = sum(session.score for session in progress)
tk.Label(
frame,
text=f"Профиль: {username}",
font=("Comic Sans MS", 30),
bg="#f8e1e1",
).pack(pady=20)
stats_text = f"Пройдено уровней: {levels_completed}\nСобрано косточек: {bones_collected}"
tk.Label(frame, text=stats_text, font=("Comic Sans MS", 20), bg="#f8e1e1").pack(pady=10)
@ -24,7 +36,7 @@ def profile_ui(root, user_id, user_app):
back_button = tk.Button(
frame,
text="Назад",
command=lambda: [clear_frame(root), user_app.show_user_dashboard()], # Очистить экран и вернуться на главное меню
command=lambda: [clear_frame(root), user_app.show_user_dashboard()],
font=("Comic Sans MS", 20)
)
back_button.pack(pady=20)

View file

@ -1,27 +1,9 @@
import logging
import time
import tkinter as tk
from src.utils import clear_frame
from database.db_events import save_progress
def start_game(root, user_id, dog_id):
"""Игровой процесс."""
clear_frame(root)
# Обратный отсчет
for i in range(3, 0, -1):
clear_frame(root)
tk.Label(root, text=f"{i}...", font=("Comic Sans MS", 30)).pack(expand=True)
root.update()
time.sleep(1) # Пауза между отсчетами
# Начало уровня
print("Начало уровня") # Для отладки
# Здесь подключается логика работы с картой и вопросами
pass
def handle_checkpoint(obstacle, current_score, root):
"""
Обрабатывает чек-поинт (косточку).

View file

@ -1,12 +1,13 @@
import logging
import os
def setup_logging():
"""Настройка логирования в файл."""
log_file = "logs/game.log"
if not os.path.exists(log_file):
os.makedirs(log_file)
log_dir = "logs"
if not os.path.exists(log_dir):
os.makedirs(log_dir) # Создание директории, если она не существует
log_file = os.path.join(log_dir, "game.log")
logging.basicConfig(
filename=log_file,