import tkinter as tk
from tkinter import messagebox
import random
import time
from collections import Counter
# ============================================================
# ACAAN MASTER TRAINER
# 카드 52장 × 숫자 3~27 = 1,300개 조합
# Python 3 + Tkinter만 사용
# ============================================================
SUITS = ["♠", "♥", "♦", "♣"]
RANKS = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
CARDS = [rank + suit for suit in SUITS for rank in RANKS]
NUMBERS = list(range(3, 28))
ALL_PAIRS = [(card, number) for card in CARDS for number in NUMBERS]
SUIT_KR = {"♠": "스페이드", "♥": "하트", "♦": "다이아", "♣": "클로버"}
RANK_KR = {"A": "에이스", "J": "잭", "Q": "퀸", "K": "킹"}
class ACAANApp:
def __init__(self, root):
self.root = root
self.root.title("ACAAN MASTER TRAINER")
self.root.geometry("720x820")
self.root.minsize(620, 720)
self.root.configure(bg="#0d1117")
self.used_pairs = set()
self.history = []
self.card_counts = Counter()
self.number_counts = Counter()
self.current_card = None
self.current_number = None
self.start_time = None
self.timer_job = None
self.revealed = True
self.difficulty = "보통"
self.build()
self.new_round()
def build(self):
# Header
header = tk.Frame(self.root, bg="#0d1117")
header.pack(fill="x", padx=24, pady=(18, 8))
tk.Label(
header, text="♠ ACAAN MASTER TRAINER ♥",
font=("Arial", 25, "bold"), fg="white", bg="#0d1117"
).pack()
tk.Label(
header, text="실전 아칸 연습 · 52장 × 숫자 3~27",
font=("Arial", 11), fg="#8d99aa", bg="#0d1117"
).pack(pady=(4, 0))
# Difficulty
diff = tk.Frame(self.root, bg="#151b24")
diff.pack(fill="x", padx=24, pady=8)
tk.Label(
diff, text="난이도",
font=("Arial", 11, "bold"), fg="#dce3ee", bg="#151b24"
).pack(side="left", padx=(14, 8), pady=10)
self.diff_var = tk.StringVar(value="보통")
for name in ["쉬움", "보통", "실전"]:
tk.Radiobutton(
diff, text=name, variable=self.diff_var, value=name,
command=self.change_difficulty,
font=("Arial", 10, "bold"),
fg="#dce3ee", bg="#151b24",
selectcolor="#283244", activebackground="#151b24",
activeforeground="white"
).pack(side="left", padx=8)
# Main challenge
box = tk.Frame(
self.root, bg="#181f2a",
highlightthickness=1, highlightbackground="#303a4c"
)
box.pack(fill="x", padx=24, pady=10)
tk.Label(
box, text="관객이 고른 카드",
font=("Arial", 11, "bold"),
fg="#8d99aa", bg="#181f2a"
).pack(pady=(17, 2))
self.card_label = tk.Label(
box, text="", font=("Arial", 48, "bold"),
bg="#181f2a"
)
self.card_label.pack()
self.card_kr = tk.Label(
box, text="", font=("Arial", 12),
fg="#aeb9c9", bg="#181f2a"
)
self.card_kr.pack()
tk.Label(
box, text="선택된 숫자",
font=("Arial", 11, "bold"),
fg="#8d99aa", bg="#181f2a"
).pack(pady=(14, 0))
self.number_label = tk.Label(
box, text="", font=("Arial", 57, "bold"),
fg="#65e6a5", bg="#181f2a"
)
self.number_label.pack(pady=(0, 3))
self.timer_label = tk.Label(
box, text="시간 0.00초",
font=("Arial", 12, "bold"),
fg="#f0c96b", bg="#181f2a"
)
self.timer_label.pack(pady=(0, 7))
self.info_label = tk.Label(
box, text="",
font=("Arial", 10), fg="#69768a", bg="#181f2a"
)
self.info_label.pack(pady=(0, 15))
# Controls
controls = tk.Frame(self.root, bg="#0d1117")
controls.pack(pady=10)
self.next_button = tk.Button(
controls, text="🎲 새 문제",
command=self.new_round,
font=("Arial", 13, "bold"),
bg="#4d6fff", fg="white", activebackground="#6983ff",
relief="flat", padx=25, pady=11, cursor="hand2"
)
self.next_button.grid(row=0, column=0, padx=5)
self.hide_button = tk.Button(
controls, text="🙈 가리기",
command=self.hide_reveal,
font=("Arial", 13, "bold"),
bg="#293343", fg="white", activebackground="#39465a",
relief="flat", padx=22, pady=11, cursor="hand2"
)
self.hide_button.grid(row=0, column=1, padx=5)
tk.Button(
controls, text="✓ 성공",
command=lambda: self.finish(True),
font=("Arial", 13, "bold"),
bg="#217852", fg="white", activebackground="#2c9668",
relief="flat", padx=20, pady=11, cursor="hand2"
).grid(row=0, column=2, padx=5)
tk.Button(
controls, text="✕ 실패",
command=lambda: self.finish(False),
font=("Arial", 13, "bold"),
bg="#863c4c", fg="white", activebackground="#a84b5e",
relief="flat", padx=20, pady=11, cursor="hand2"
).grid(row=0, column=3, padx=5)
# Progress
prog = tk.Frame(self.root, bg="#151b24")
prog.pack(fill="x", padx=24, pady=8)
self.progress_text = tk.Label(
prog, text="", font=("Arial", 11, "bold"),
fg="#dce3ee", bg="#151b24"
)
self.progress_text.pack(pady=(11, 2))
self.progress = tk.Canvas(
prog, height=12, bg="#252d3a", highlightthickness=0
)
self.progress.pack(fill="x", padx=14, pady=(3, 13))
self.progress.bind("", lambda e: self.draw_progress())
# Stats row
stats = tk.Frame(self.root, bg="#0d1117")
stats.pack(fill="x", padx=24, pady=8)
self.stats_label = tk.Label(
stats, text="", font=("Arial", 11),
fg="#aeb9c9", bg="#0d1117", justify="left"
)
self.stats_label.pack(side="left")
tk.Button(
stats, text="📊 통계",
command=self.show_stats,
font=("Arial", 10, "bold"),
bg="#252e3d", fg="white", relief="flat",
padx=15, pady=8, cursor="hand2"
).pack(side="right")
# Quick explanation
tip = tk.Frame(self.root, bg="#111722")
tip.pack(fill="x", padx=24, pady=8)
tk.Label(
tip,
text="연습법: 카드와 숫자를 확인 → 실제 아칸 루틴 수행 → 성공/실패 기록",
font=("Arial", 10),
fg="#8290a5", bg="#111722"
).pack(pady=10)
self.update_stats()
def change_difficulty(self):
self.difficulty = self.diff_var.get()
self.new_round()
def new_round(self):
self.stop_timer()
# 모든 1,300개 조합을 한 번씩 사용한 뒤 새 사이클
if len(self.used_pairs) >= len(ALL_PAIRS):
self.used_pairs.clear()
messagebox.showinfo(
"🎉 1사이클 완료!",
"1,300개의 카드+숫자 조합을 모두 연습했습니다!\n새 사이클을 시작합니다."
)
remaining = [x for x in ALL_PAIRS if x not in self.used_pairs]
self.current_card, self.current_number = random.choice(remaining)
self.used_pairs.add((self.current_card, self.current_number))
self.revealed = True
self.render_challenge()
self.start_time = time.perf_counter()
self.tick_timer()
def render_challenge(self):
suit = self.current_card[-1]
card_color = "#ef6678" if suit in "♥♦" else "#f3f5f8"
self.card_label.config(text=self.current_card, fg=card_color)
self.card_kr.config(text=self.card_to_kr(self.current_card))
self.number_label.config(text=str(self.current_number))
self.hide_button.config(text="🙈 가리기")
self.info_label.config(
text=f"남은 미사용 조합: {len(ALL_PAIRS) - len(self.used_pairs)}"
)
if self.difficulty == "쉬움":
self.card_label.config(font=("Arial", 48, "bold"))
self.number_label.config(font=("Arial", 57, "bold"))
elif self.difficulty == "보통":
self.card_label.config(font=("Arial", 48, "bold"))
self.number_label.config(font=("Arial", 57, "bold"))
else:
# 실전 모드: 짧게 보고 가리는 연습
self.root.after(1300, self.auto_hide)
self.draw_progress()
def auto_hide(self):
if self.revealed and self.current_card is not None:
self.hide_reveal(force_hide=True)
def hide_reveal(self, force_hide=False):
if self.current_card is None:
return
if force_hide or self.revealed:
self.card_label.config(text="?", fg="#778399")
self.card_kr.config(text="카드 숨김")
self.number_label.config(text="?", fg="#65e6a5")
self.hide_button.config(text="👁 다시 보기")
self.revealed = False
else:
suit = self.current_card[-1]
self.card_label.config(
text=self.current_card,
fg="#ef6678" if suit in "♥♦" else "#f3f5f8"
)
self.card_kr.config(text=self.card_to_kr(self.current_card))
self.number_label.config(text=str(self.current_number))
self.hide_button.config(text="🙈 가리기")
self.revealed = True
def tick_timer(self):
if self.start_time is None:
return
elapsed = time.perf_counter() - self.start_time
self.timer_label.config(text=f"시간 {elapsed:.2f}초")
self.timer_job = self.root.after(50, self.tick_timer)
def stop_timer(self):
if self.timer_job:
self.root.after_cancel(self.timer_job)
self.timer_job = None
def finish(self, success):
if self.current_card is None:
return
elapsed = time.perf_counter() - self.start_time if self.start_time else 0
self.stop_timer()
self.history.append({
"card": self.current_card,
"number": self.current_number,
"success": success,
"time": elapsed
})
self.card_counts[self.current_card] += 1
self.number_counts[self.current_number] += 1
result = "성공 ✓" if success else "실패 ✕"
self.info_label.config(
text=f"{result} · {elapsed:.2f}초 · '새 문제'를 눌러 계속"
)
self.update_stats()
def card_to_kr(self, card):
return f"{SUIT_KR[card[-1]]} {RANK_KR.get(card[:-1], card[:-1])}"
def update_stats(self):
total = len(self.history)
wins = sum(x["success"] for x in self.history)
rate = wins / total * 100 if total else 0
avg = sum(x["time"] for x in self.history) / total if total else 0
self.stats_label.config(
text=f"연습 {total}회 | 성공률 {rate:.1f}% | 평균 {avg:.2f}초"
)
self.progress_text.config(
text=f"전체 조합 진행률 {len(self.used_pairs):,} / 1,300"
)
self.draw_progress()
def draw_progress(self):
self.progress.delete("all")
w = max(self.progress.winfo_width(), 1)
ratio = min(len(self.used_pairs) / 1300, 1)
self.progress.create_rectangle(
0, 0, w * ratio, 12, fill="#4d6fff", outline=""
)
def show_stats(self):
total = len(self.history)
wins = sum(x["success"] for x in self.history)
losses = total - wins
rate = wins / total * 100 if total else 0
avg = sum(x["time"] for x in self.history) / total if total else 0
card_top = self.card_counts.most_common(5)
num_top = self.number_counts.most_common(5)
cards = "\n".join(
f" {c}: {n}회" for c, n in card_top
) or " 기록 없음"
nums = "\n".join(
f" {n}: {c}회" for n, c in num_top
) or " 기록 없음"
text = (
"🃏 ACAAN MASTER 통계\n\n"
f"총 연습: {total}회\n"
f"성공: {wins}회\n"
f"실패: {losses}회\n"
f"성공률: {rate:.1f}%\n"
f"평균 기록 시간: {avg:.2f}초\n\n"
f"이번 사이클: {len(self.used_pairs):,}/1,300\n\n"
"자주 연습한 카드 TOP 5\n" + cards +
"\n\n자주 연습한 숫자 TOP 5\n" + nums
)
if messagebox.askyesno(
"통계",
text + "\n\n연습 기록을 초기화할까요?"
):
self.history.clear()
self.card_counts.clear()
self.number_counts.clear()
self.update_stats()
messagebox.showinfo("완료", "연습 기록을 초기화했습니다.")
if __name__ == "__main__":
root = tk.Tk()
app = ACAANApp(root)
root.mainloop()