Changed Hints and Skills to Skill Search

This commit is contained in:
kiyreload27
2025-12-28 17:35:54 +00:00
parent 4b944c1a53
commit e0eab4dc61
3 changed files with 268 additions and 149 deletions

View File

@@ -700,3 +700,102 @@ def get_database_stats():
conn.close() conn.close()
return stats return stats
# ============================================
# Skill Search Queries
# ============================================
def get_all_unique_skills():
"""Get a sorted list of all unique skills from hints and events"""
conn = get_conn()
cur = conn.cursor()
# Get skills from hints
cur.execute("SELECT DISTINCT hint_name FROM support_hints")
hint_skills = {row[0] for row in cur.fetchall() if row[0]}
# Get skills from events
cur.execute("SELECT DISTINCT skill_name FROM event_skills")
event_skills = {row[0] for row in cur.fetchall() if row[0]}
# Combine and sort
all_skills = sorted(list(hint_skills.union(event_skills)))
conn.close()
return all_skills
def get_cards_with_skill(skill_name):
"""
Find all cards that have a specific skill.
Returns list of dicts:
{
'card_id': int,
'name': str,
'rarity': str,
'type': str,
'image_path': str,
'source': str ('Hint' or 'Event: [Name]'),
'details': str (description or event name)
}
"""
conn = get_conn()
cur = conn.cursor()
results = []
seen_entries = set() # To avoid duplicates if same skill in multiple events
# 1. Check Hints
cur.execute("""
SELECT sc.card_id, sc.name, sc.rarity, sc.card_type, sc.image_path, sh.hint_description
FROM support_hints sh
JOIN support_cards sc ON sh.card_id = sc.card_id
WHERE sh.hint_name = ?
""", (skill_name,))
for row in cur.fetchall():
entry_key = (row[0], 'Hint')
if entry_key not in seen_entries:
results.append({
'card_id': row[0],
'name': row[1],
'rarity': row[2],
'type': row[3],
'image_path': row[4],
'source': 'Training Hint',
'details': row[5] or "Random hint event"
})
seen_entries.add(entry_key)
# 2. Check Event Skills
cur.execute("""
SELECT sc.card_id, sc.name, sc.rarity, sc.card_type, sc.image_path, se.event_name
FROM event_skills es
JOIN support_events se ON es.event_id = se.event_id
JOIN support_cards sc ON se.card_id = sc.card_id
WHERE es.skill_name = ?
""", (skill_name,))
for row in cur.fetchall():
# Clean event name if it has newlines or excessive spaces
event_name = row[5].replace('\n', ' ').strip()
entry_key = (row[0], f'Event: {event_name}')
if entry_key not in seen_entries:
results.append({
'card_id': row[0],
'name': row[1],
'rarity': row[2],
'type': row[3],
'image_path': row[4],
'source': 'Event',
'details': event_name
})
seen_entries.add(entry_key)
conn.close()
# Sort by Rarity (SSR first), then Name
rarity_map = {'SSR': 3, 'SR': 2, 'R': 1}
results.sort(key=lambda x: (rarity_map.get(x['rarity'], 0), x['name']), reverse=True)
return results

View File

@@ -1,180 +1,200 @@
""" """
Hints and Skills View - Display support hints and event skills Skill Search View - Find cards by the skills they teach
""" """
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
import sys import sys
import os import os
from PIL import Image, ImageTk
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from db.db_queries import get_hints, get_events, get_all_event_skills, get_card_by_id from db.db_queries import get_all_unique_skills, get_cards_with_skill
from utils import resolve_image_path
from gui.theme import ( from gui.theme import (
BG_DARK, BG_MEDIUM, BG_LIGHT, BG_DARK, BG_MEDIUM, BG_LIGHT, BG_HIGHLIGHT,
ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_TERTIARY, ACCENT_SUCCESS, ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_TERTIARY,
TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED, TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED,
FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL, FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL,
create_styled_text, create_card_frame create_card_frame, get_type_icon, create_styled_button
) )
class HintsSkillsFrame(ttk.Frame): class SkillSearchFrame(ttk.Frame):
"""Frame for viewing support hints and event skills""" """Frame for searching skills and finding cards that have them"""
def __init__(self, parent): def __init__(self, parent):
super().__init__(parent) super().__init__(parent)
self.current_card_id = None self.all_skills = []
self.current_card_name = None self.icon_cache = {}
self.create_widgets() self.create_widgets()
self.load_skills()
def create_widgets(self): def create_widgets(self):
"""Create the hints and skills interface""" """Create the skill search interface"""
# Header # Main split container
header_frame = tk.Frame(self, bg=BG_DARK) main_pane = ttk.PanedWindow(self, orient=tk.HORIZONTAL)
header_frame.pack(fill=tk.X, padx=20, pady=15) main_pane.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self.card_label = tk.Label(header_frame, # === Left Panel: Skill List ===
text="💡 Select a card from the Card List tab", left_frame = tk.Frame(main_pane, bg=BG_DARK, width=300)
font=FONT_HEADER, bg=BG_DARK, fg=ACCENT_PRIMARY) main_pane.add(left_frame, weight=1)
self.card_label.pack(side=tk.LEFT)
# Main content with two columns # Search Header
content_frame = tk.Frame(self, bg=BG_DARK) header = tk.Frame(left_frame, bg=BG_DARK)
content_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 15)) header.pack(fill=tk.X, pady=(0, 10))
tk.Label(header, text="🔍 Search Skills", font=FONT_HEADER,
# Left column: Hints
left_container = tk.Frame(content_frame, bg=BG_DARK)
left_container.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 10))
hints_header = tk.Frame(left_container, bg=BG_DARK)
hints_header.pack(fill=tk.X, pady=(0, 8))
tk.Label(hints_header, text="🎯 Training Hints", font=FONT_SUBHEADER,
bg=BG_DARK, fg=TEXT_PRIMARY).pack(side=tk.LEFT) bg=BG_DARK, fg=TEXT_PRIMARY).pack(side=tk.LEFT)
hints_frame = create_card_frame(left_container) # Search Entry
hints_frame.pack(fill=tk.BOTH, expand=True) self.search_var = tk.StringVar()
self.search_var.trace('w', self.filter_skills)
self.hints_text = create_styled_text(hints_frame, height=18) search_entry = ttk.Entry(left_frame, textvariable=self.search_var)
self.hints_text.pack(fill=tk.BOTH, expand=True, padx=2, pady=2) search_entry.pack(fill=tk.X, padx=(0, 5), pady=(0, 10))
self.hints_text.config(state=tk.DISABLED)
# Configure tags for hints # Skill Listbox
self.hints_text.tag_configure('header', font=FONT_SUBHEADER, foreground=ACCENT_PRIMARY) list_container = create_card_frame(left_frame)
self.hints_text.tag_configure('skill', foreground=ACCENT_TERTIARY, font=FONT_BODY_BOLD) list_container.pack(fill=tk.BOTH, expand=True)
self.hints_text.tag_configure('desc', foreground=TEXT_MUTED)
self.hints_text.tag_configure('number', foreground=ACCENT_SECONDARY)
# Right column: Events and Skills scrollbar = ttk.Scrollbar(list_container, orient=tk.VERTICAL)
right_container = tk.Frame(content_frame, bg=BG_DARK) self.skill_listbox = tk.Listbox(list_container,
right_container.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) bg=BG_MEDIUM, fg=TEXT_SECONDARY,
selectbackground=ACCENT_PRIMARY,
selectforeground=TEXT_PRIMARY,
highlightthickness=0, bd=0,
font=FONT_BODY,
yscrollcommand=scrollbar.set)
events_header = tk.Frame(right_container, bg=BG_DARK) scrollbar.config(command=self.skill_listbox.yview)
events_header.pack(fill=tk.X, pady=(0, 8))
tk.Label(events_header, text="📅 Training Events & Skills", font=FONT_SUBHEADER,
bg=BG_DARK, fg=TEXT_PRIMARY).pack(side=tk.LEFT)
events_frame = create_card_frame(right_container) self.skill_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)
events_frame.pack(fill=tk.BOTH, expand=True)
tree_container = tk.Frame(events_frame, bg=BG_MEDIUM)
tree_container.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
# Treeview for events
self.events_tree = ttk.Treeview(tree_container, columns=('event', 'skills'), show='tree headings')
self.events_tree.heading('#0', text='')
self.events_tree.heading('event', text='Event/Skill')
self.events_tree.heading('skills', text='Details')
self.events_tree.column('#0', width=35)
self.events_tree.column('event', width=240)
self.events_tree.column('skills', width=180)
scrollbar = ttk.Scrollbar(tree_container, orient=tk.VERTICAL, command=self.events_tree.yview)
self.events_tree.configure(yscrollcommand=scrollbar.set)
self.events_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y) scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Summary section at bottom self.skill_listbox.bind('<<ListboxSelect>>', self.on_skill_selected)
summary_frame = tk.Frame(self, bg=BG_MEDIUM, padx=15, pady=10)
summary_frame.pack(fill=tk.X, padx=20, pady=(0, 10))
self.summary_label = tk.Label(summary_frame, text="", font=FONT_SMALL, # === Right Panel: Results ===
bg=BG_MEDIUM, fg=TEXT_SECONDARY) right_frame = tk.Frame(main_pane, bg=BG_DARK)
self.summary_label.pack() main_pane.add(right_frame, weight=3)
# Result Header
self.result_header = tk.Label(right_frame, text="Select a skill to see who has it",
font=FONT_HEADER, bg=BG_DARK, fg=ACCENT_TERTIARY)
self.result_header.pack(anchor='w', pady=(0, 15), padx=10)
# Results Treeview
tree_frame = create_card_frame(right_frame)
tree_frame.pack(fill=tk.BOTH, expand=True, padx=10)
cols = ('name', 'rarity', 'type', 'source', 'details')
self.tree = ttk.Treeview(tree_frame, columns=cols, show='tree headings',
style="Treeview")
self.tree.heading('#0', text='')
self.tree.heading('name', text='Card Name')
self.tree.heading('rarity', text='Rarity')
self.tree.heading('type', text='Type')
self.tree.heading('source', text='Source')
self.tree.heading('details', text='Details')
self.tree.column('#0', width=50, anchor='center')
self.tree.column('name', width=180)
self.tree.column('rarity', width=50, anchor='center')
self.tree.column('type', width=80, anchor='center')
self.tree.column('source', width=100)
self.tree.column('details', width=250)
vsb = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL, command=self.tree.yview)
self.tree.configure(yscrollcommand=vsb.set)
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)
vsb.pack(side=tk.RIGHT, fill=tk.Y, pady=2)
# Stats footer
self.stats_label = tk.Label(right_frame, text="", font=FONT_SMALL,
bg=BG_DARK, fg=TEXT_MUTED)
self.stats_label.pack(anchor='e', pady=5, padx=10)
def load_skills(self):
"""Load all unique skills into listbox"""
self.all_skills = get_all_unique_skills()
self.update_listbox(self.all_skills)
def update_listbox(self, items):
"""Update listbox content"""
self.skill_listbox.delete(0, tk.END)
for item in items:
self.skill_listbox.insert(tk.END, item)
def filter_skills(self, *args):
"""Filter skills based on search text"""
search = self.search_var.get().lower()
if not search:
self.update_listbox(self.all_skills)
return
filtered = [s for s in self.all_skills if search in s.lower()]
self.update_listbox(filtered)
def on_skill_selected(self, event):
"""Handle skill selection"""
selection = self.skill_listbox.curselection()
if not selection:
return
skill_name = self.skill_listbox.get(selection[0])
self.show_cards_for_skill(skill_name)
def show_cards_for_skill(self, skill_name):
"""Fetch and display cards with the selected skill"""
self.result_header.config(text=f"Cards with skill: {skill_name}")
# Clear tree
for item in self.tree.get_children():
self.tree.delete(item)
cards = get_cards_with_skill(skill_name)
for card in cards:
# Load Icon
card_id = card['card_id']
img = self.icon_cache.get(card_id)
if not img:
resolved_path = resolve_image_path(card['image_path'])
if resolved_path and os.path.exists(resolved_path):
try:
pil_img = Image.open(resolved_path)
pil_img.thumbnail((32, 32), Image.Resampling.LANCZOS)
img = ImageTk.PhotoImage(pil_img)
self.icon_cache[card_id] = img
except:
pass
type_display = f"{get_type_icon(card['type'])} {card['type']}"
values = (
card['name'],
card['rarity'],
type_display,
card['source'],
card['details']
)
if img:
self.tree.insert('', tk.END, text='', image=img, values=values)
else:
self.tree.insert('', tk.END, text='?', values=values)
self.stats_label.config(text=f"Found {len(cards)} cards")
def set_card(self, card_id): def set_card(self, card_id):
"""Load a card's hints and skills""" """
self.current_card_id = card_id Legacy method compatibility for main window calls.
We don't need to do anything here since we are skill-centric now.
# Get card info But main_window might call it when card is selected in tab 1.
card = get_card_by_id(card_id) We could potentially auto-search a skill from that card?
if card: For now, just ignore it.
self.current_card_name = card[1] """
self.card_label.config(text=f"💡 {self.current_card_name}") pass
self.update_hints_display()
self.update_events_display()
def update_hints_display(self):
"""Update the hints display"""
self.hints_text.config(state=tk.NORMAL)
self.hints_text.delete('1.0', tk.END)
if not self.current_card_id:
self.hints_text.insert(tk.END, "No card selected\n\n", 'desc')
self.hints_text.insert(tk.END, "Select a card from the Card List tab to view its hints.", 'desc')
self.hints_text.config(state=tk.DISABLED)
return
hints = get_hints(self.current_card_id)
self.hints_text.insert(tk.END, "Training Skills this card can teach:\n\n", 'header')
if hints:
for i, (hint_name, hint_desc) in enumerate(hints, 1):
self.hints_text.insert(tk.END, f" {i}. ", 'number')
self.hints_text.insert(tk.END, f"{hint_name}\n", 'skill')
if hint_desc:
self.hints_text.insert(tk.END, f" {hint_desc}\n", 'desc')
self.hints_text.insert(tk.END, "\n")
else:
self.hints_text.insert(tk.END, " No hints/skills data available.\n\n", 'desc')
self.hints_text.insert(tk.END, " This may mean:\n", 'desc')
self.hints_text.insert(tk.END, " • Card hasn't been scraped yet\n", 'desc')
self.hints_text.insert(tk.END, " • Card has no trainable skills\n", 'desc')
self.hints_text.config(state=tk.DISABLED)
def update_events_display(self):
"""Update the events tree display"""
self.events_tree.delete(*self.events_tree.get_children())
if not self.current_card_id:
return
events = get_events(self.current_card_id)
events_with_skills = get_all_event_skills(self.current_card_id)
# Add events as parent nodes
for event_id, event_name, event_type in events:
skills = events_with_skills.get(event_name, [])
skill_count = f"{len(skills)} skills" if skills else "No skills"
event_node = self.events_tree.insert('', tk.END, text='📅',
values=(event_name, skill_count))
# Add skills as children
for skill in skills:
self.events_tree.insert(event_node, tk.END, text='',
values=(skill, ''))
# Update summary
hint_count = len(get_hints(self.current_card_id))
event_count = len(events)
self.summary_label.config(
text=f"📊 Summary: {hint_count} hints │ {event_count} events"
)

View File

@@ -13,7 +13,7 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from db.db_queries import get_database_stats, get_owned_count from db.db_queries import get_database_stats, get_owned_count
from gui.card_view import CardListFrame from gui.card_view import CardListFrame
from gui.effects_view import EffectsFrame from gui.effects_view import EffectsFrame
from gui.hints_skills_view import HintsSkillsFrame from gui.hints_skills_view import SkillSearchFrame
from gui.deck_builder import DeckBuilderFrame from gui.deck_builder import DeckBuilderFrame
from gui.update_dialog import show_update_dialog from gui.update_dialog import show_update_dialog
from gui.theme import ( from gui.theme import (
@@ -33,7 +33,7 @@ class MainWindow:
def __init__(self): def __init__(self):
self.root = tk.Tk() self.root = tk.Tk()
self.root.title("Umamusume Support Card Manager") self.root.title("Umamusume Support Card Manager")
self.root.geometry("1350x800") self.root.geometry("1400x850") # Slightly larger for new UI
self.root.minsize(1350, 800) self.root.minsize(1350, 800)
# Set icon # Set icon
@@ -157,9 +157,9 @@ class MainWindow:
self.deck_frame = DeckBuilderFrame(self.notebook) self.deck_frame = DeckBuilderFrame(self.notebook)
self.notebook.add(self.deck_frame, text=" 🎴 Deck Builder ") self.notebook.add(self.deck_frame, text=" 🎴 Deck Builder ")
# Hints & Skills Tab # Skill Search Tab (Replaces Hints & Skills)
self.hints_frame = HintsSkillsFrame(self.notebook) self.hints_frame = SkillSearchFrame(self.notebook)
self.notebook.add(self.hints_frame, text=" 💡 Hints & Skills ") self.notebook.add(self.hints_frame, text=" 🔍 Skill Search ")
def create_status_bar(self, parent): def create_status_bar(self, parent):
"""Create status bar at bottom""" """Create status bar at bottom"""