open-npc / battle_arena.py
seawolf2357's picture
Update battle_arena.py
9432b16 verified
import aiosqlite
import random
from datetime import datetime, timedelta
from typing import Dict, List, Tuple, Optional
async def init_battle_arena_db(db_path: str):
"""๋ฐฐํ‹€ ์•„๋ ˆ๋‚˜ ํ…Œ์ด๋ธ” ์ดˆ๊ธฐํ™” (DB ๋ฝ ๋ฐฉ์ง€)"""
async with aiosqlite.connect(db_path, timeout=30.0) as db:
await db.execute("PRAGMA journal_mode=WAL")
await db.execute("PRAGMA busy_timeout=30000") # 30์ดˆ ๋Œ€๊ธฐ
await db.execute("""
CREATE TABLE IF NOT EXISTS battle_rooms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
creator_agent_id TEXT,
creator_email TEXT,
title TEXT NOT NULL,
option_a TEXT NOT NULL,
option_b TEXT NOT NULL,
battle_type TEXT DEFAULT 'opinion',
duration_hours INTEGER DEFAULT 24,
end_time TIMESTAMP NOT NULL,
total_pool INTEGER DEFAULT 0,
option_a_pool INTEGER DEFAULT 0,
option_b_pool INTEGER DEFAULT 0,
status TEXT DEFAULT 'active',
winner TEXT,
resolved_at TIMESTAMP,
admin_result TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (creator_agent_id) REFERENCES npc_agents(agent_id),
FOREIGN KEY (creator_email) REFERENCES user_profiles(email)
)
""")
await db.execute("""
CREATE TABLE IF NOT EXISTS battle_bets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id INTEGER NOT NULL,
bettor_agent_id TEXT,
bettor_email TEXT,
choice TEXT NOT NULL,
bet_amount INTEGER NOT NULL,
payout INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (room_id) REFERENCES battle_rooms(id),
FOREIGN KEY (bettor_agent_id) REFERENCES npc_agents(agent_id),
FOREIGN KEY (bettor_email) REFERENCES user_profiles(email)
)
""")
await db.execute("CREATE INDEX IF NOT EXISTS idx_battle_rooms_status ON battle_rooms(status)")
await db.execute("CREATE INDEX IF NOT EXISTS idx_battle_bets_room ON battle_bets(room_id)")
await db.commit()
async def create_battle_room(
db_path: str,
creator_id: str,
is_npc: bool,
title: str,
option_a: str,
option_b: str,
duration_hours: int = 24,
battle_type: str = 'opinion'
) -> Tuple[bool, str, Optional[int]]:
"""๋ฐฐํ‹€ ๋ฐฉ ์ƒ์„ฑ (50 GPU ์†Œ๋ชจ)
battle_type:
- 'opinion': ๋‹ค์ˆ˜๊ฒฐ ํŒ์ • (์ฃผ๊ด€์  ์˜๊ฒฌ, NPC ์ „์šฉ)
- 'prediction': ์‹ค์ œ ๊ฒฐ๊ณผ ํŒ์ • (๊ฐ๊ด€์  ์˜ˆ์ธก, ์‚ฌ์šฉ์ž ์ „์šฉ)
duration_hours: 1์‹œ๊ฐ„ ~ 365์ผ(8760์‹œ๊ฐ„)
"""
if not title or len(title) < 10:
return False, "โŒ ์ œ๋ชฉ 10์ž ์ด์ƒ", None
if not option_a or not option_b:
return False, "โŒ ์„ ํƒ์ง€ A/B ํ•„์š”", None
if duration_hours < 1 or duration_hours > 8760:
return False, "โŒ ๊ธฐํ•œ 1์‹œ๊ฐ„~365์ผ", None
if is_npc and battle_type != 'opinion':
return False, "โŒ NPC๋Š” ๋‹ค์ˆ˜๊ฒฐ ์ฃผ์ œ๋งŒ ๊ฐ€๋Šฅ", None
async with aiosqlite.connect(db_path, timeout=30.0) as db:
await db.execute("PRAGMA busy_timeout=30000")
if is_npc:
cursor = await db.execute(
"SELECT gpu_dollars FROM npc_agents WHERE agent_id=?", (creator_id,)
)
else:
cursor = await db.execute(
"SELECT gpu_dollars FROM user_profiles WHERE email=?", (creator_id,)
)
row = await cursor.fetchone()
if not row or row[0] < 50:
return False, "โŒ GPU ๋ถ€์กฑ (50 ํ•„์š”)", None
end_time = datetime.now() + timedelta(hours=duration_hours)
if is_npc:
await db.execute(
"""INSERT INTO battle_rooms
(creator_agent_id, title, option_a, option_b, battle_type, duration_hours, end_time)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
(creator_id, title, option_a, option_b, battle_type, duration_hours, end_time.isoformat())
)
await db.execute(
"UPDATE npc_agents SET gpu_dollars=gpu_dollars-50 WHERE agent_id=?",
(creator_id,)
)
else:
await db.execute(
"""INSERT INTO battle_rooms
(creator_email, title, option_a, option_b, battle_type, duration_hours, end_time)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
(creator_id, title, option_a, option_b, battle_type, duration_hours, end_time.isoformat())
)
await db.execute(
"UPDATE user_profiles SET gpu_dollars=gpu_dollars-50 WHERE email=?",
(creator_id,)
)
await db.commit()
cursor = await db.execute("SELECT last_insert_rowid()")
room_id = (await cursor.fetchone())[0]
type_emoji = '๐Ÿ’ญ' if battle_type == 'opinion' else '๐Ÿ”ฎ'
if duration_hours >= 24:
days = duration_hours // 24
hours = duration_hours % 24
if hours > 0:
duration_str = f"{days}์ผ {hours}์‹œ๊ฐ„"
else:
duration_str = f"{days}์ผ"
else:
duration_str = f"{duration_hours}์‹œ๊ฐ„"
return True, f"โœ… {type_emoji} ๋ฐฐํ‹€ ๋ฐฉ ์ƒ์„ฑ! (ID: {room_id}, ๊ธฐํ•œ: {duration_str})", room_id
async def place_bet(
db_path: str,
room_id: int,
bettor_id: str,
is_npc: bool,
choice: str,
bet_amount: int
) -> Tuple[bool, str]:
"""๋ฒ ํŒ… ์‹คํ–‰ (1~100 GPU ๋žœ๋ค ๋ฒ ํŒ…)"""
if choice not in ['A', 'B']:
return False, "โŒ A ๋˜๋Š” B ์„ ํƒ"
if bet_amount < 1 or bet_amount > 100:
return False, "โŒ ๋ฒ ํŒ… 1~100 GPU"
async with aiosqlite.connect(db_path, timeout=30.0) as db:
await db.execute("PRAGMA busy_timeout=30000")
db.row_factory = aiosqlite.Row
# ๋ฐฐํ‹€ ๋ฐฉ ํ™•์ธ
cursor = await db.execute(
"SELECT * FROM battle_rooms WHERE id=? AND status='active'",
(room_id,)
)
room = await cursor.fetchone()
if not room:
return False, "โŒ ๋ฐฉ ์—†์Œ or ๋งˆ๊ฐ"
room = dict(room)
end_time = datetime.fromisoformat(room['end_time'])
if datetime.now() >= end_time:
return False, "โŒ ๋ฒ ํŒ… ๋งˆ๊ฐ"
# ์ค‘๋ณต ๋ฒ ํŒ… ์ฒดํฌ
if is_npc:
cursor = await db.execute(
"SELECT id FROM battle_bets WHERE room_id=? AND bettor_agent_id=?",
(room_id, bettor_id)
)
else:
cursor = await db.execute(
"SELECT id FROM battle_bets WHERE room_id=? AND bettor_email=?",
(room_id, bettor_id)
)
existing_bet = await cursor.fetchone()
if existing_bet:
return False, "โŒ ์ด๋ฏธ ๋ฒ ํŒ…ํ•˜์…จ์Šต๋‹ˆ๋‹ค"
# GPU ํ™•์ธ ๋ฐ ์ฐจ๊ฐ
if is_npc:
cursor = await db.execute(
"SELECT gpu_dollars FROM npc_agents WHERE agent_id=?",
(bettor_id,)
)
user_row = await cursor.fetchone()
if not user_row or user_row[0] < bet_amount:
return False, "โŒ GPU ๋ถ€์กฑ"
await db.execute(
"UPDATE npc_agents SET gpu_dollars=gpu_dollars-? WHERE agent_id=?",
(bet_amount, bettor_id)
)
else:
cursor = await db.execute(
"SELECT gpu_dollars FROM user_profiles WHERE email=?",
(bettor_id,)
)
user_row = await cursor.fetchone()
if not user_row:
return False, f"โŒ ์‚ฌ์šฉ์ž ์—†์Œ ({bettor_id})"
if user_row[0] < bet_amount:
return False, f"โŒ GPU ๋ถ€์กฑ (๋ณด์œ : {user_row[0]}, ํ•„์š”: {bet_amount})"
await db.execute(
"UPDATE user_profiles SET gpu_dollars=gpu_dollars-? WHERE email=?",
(bet_amount, bettor_id)
)
# ๋ฒ ํŒ… ๊ธฐ๋ก
if is_npc:
await db.execute(
"""INSERT INTO battle_bets
(room_id, bettor_agent_id, choice, bet_amount)
VALUES (?, ?, ?, ?)""",
(room_id, bettor_id, choice, bet_amount)
)
else:
await db.execute(
"""INSERT INTO battle_bets
(room_id, bettor_email, choice, bet_amount)
VALUES (?, ?, ?, ?)""",
(room_id, bettor_id, choice, bet_amount)
)
# ๋ฐฐํ‹€ ํ’€ ์—…๋ฐ์ดํŠธ
if choice == 'A':
await db.execute(
"""UPDATE battle_rooms
SET total_pool=total_pool+?, option_a_pool=option_a_pool+?
WHERE id=?""",
(bet_amount, bet_amount, room_id)
)
else:
await db.execute(
"""UPDATE battle_rooms
SET total_pool=total_pool+?, option_b_pool=option_b_pool+?
WHERE id=?""",
(bet_amount, bet_amount, room_id)
)
await db.commit()
return True, f"โœ… {choice} ๋ฒ ํŒ… ์™„๋ฃŒ! ({bet_amount} GPU)"
async def set_battle_result(
db_path: str,
room_id: int,
admin_email: str,
winner: str # 'A' or 'B' or 'draw'
) -> Tuple[bool, str]:
"""๊ด€๋ฆฌ์ž๊ฐ€ prediction ๋ฐฐํ‹€์˜ ์‹ค์ œ ๊ฒฐ๊ณผ ์„ค์ •
Args:
db_path: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฒฝ๋กœ
room_id: ๋ฐฐํ‹€๋ฐฉ ID
admin_email: ๊ด€๋ฆฌ์ž ์ด๋ฉ”์ผ (๊ฒ€์ฆ์šฉ)
winner: 'A', 'B', 'draw' ์ค‘ ํ•˜๋‚˜
Returns:
(์„ฑ๊ณต์—ฌ๋ถ€, ๋ฉ”์‹œ์ง€)
"""
if winner not in ['A', 'B', 'draw']:
return False, "โŒ winner๋Š” 'A', 'B', 'draw' ์ค‘ ํ•˜๋‚˜์—ฌ์•ผ ํ•จ"
async with aiosqlite.connect(db_path, timeout=30.0) as db:
await db.execute("PRAGMA busy_timeout=30000")
db.row_factory = aiosqlite.Row
cursor = await db.execute(
"SELECT * FROM battle_rooms WHERE id=? AND status='active'",
(room_id,)
)
room = await cursor.fetchone()
if not room:
return False, "โŒ ํ™œ์„ฑ ๋ฐฐํ‹€์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค"
room = dict(room)
# prediction ํƒ€์ž…๋งŒ ๊ด€๋ฆฌ์ž ๊ฒฐ๊ณผ ์„ค์ • ๊ฐ€๋Šฅ
if room['battle_type'] != 'prediction':
return False, "โŒ opinion ๋ฐฐํ‹€์€ ์ž๋™ ํŒ์ •๋ฉ๋‹ˆ๋‹ค"
# ๊ฒฐ๊ณผ ์ €์žฅ
await db.execute(
"UPDATE battle_rooms SET admin_result=? WHERE id=?",
(winner, room_id)
)
await db.commit()
# ์•„์ง ๋งˆ๊ฐ ์ „์ด๋ฉด ๊ฒฐ๊ณผ๋งŒ ์ €์žฅํ•˜๊ณ  ๋Œ€๊ธฐ
end_time = datetime.fromisoformat(room['end_time'])
if datetime.now() < end_time:
option_name = room['option_a'] if winner == 'A' else room['option_b'] if winner == 'B' else '๋ฌด์Šน๋ถ€'
remaining = end_time - datetime.now()
if remaining.days > 0:
time_str = f"{remaining.days}์ผ {int(remaining.seconds//3600)}์‹œ๊ฐ„"
else:
time_str = f"{int(remaining.seconds//3600)}์‹œ๊ฐ„"
return True, f"โœ… ๊ฒฐ๊ณผ ์„ค์ •: '{option_name}' (๋ฒ ํŒ… ๋งˆ๊ฐ ํ›„ ์ž๋™ ํŒ์ •, ๋‚จ์€ ์‹œ๊ฐ„: {time_str})"
# ๋งˆ๊ฐ ํ›„๋ผ๋ฉด ์ฆ‰์‹œ ํŒ์ •
return await resolve_battle(db_path, room_id)
async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
"""๋ฐฐํ‹€ ํŒ์ • (ํƒ€์ž…์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋กœ์ง ์ ์šฉ)
- opinion: 50.01% ์ด์ƒ ๋“ํ‘œํ•œ ์ชฝ ์Šน๋ฆฌ
- prediction: ๊ด€๋ฆฌ์ž๊ฐ€ ์„ค์ •ํ•œ ์‹ค์ œ ๊ฒฐ๊ณผ๋กœ ํŒ์ •
"""
async with aiosqlite.connect(db_path, timeout=30.0) as db:
await db.execute("PRAGMA busy_timeout=30000")
db.row_factory = aiosqlite.Row
cursor = await db.execute(
"SELECT * FROM battle_rooms WHERE id=? AND status='active'",
(room_id,)
)
room = await cursor.fetchone()
if not room:
return False, "โŒ ํ™œ์„ฑ ๋ฐฐํ‹€์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค"
room = dict(room)
end_time = datetime.fromisoformat(room['end_time'])
if datetime.now() < end_time:
return False, "โŒ ์•„์ง ๋ฒ ํŒ… ์ค‘์ž…๋‹ˆ๋‹ค"
total_pool = room['total_pool']
option_a_pool = room['option_a_pool']
option_b_pool = room['option_b_pool']
# ๋ฒ ํŒ…์ด ์—†์œผ๋ฉด ๋ฌด์Šน๋ถ€ ์ฒ˜๋ฆฌ
if total_pool == 0:
await db.execute(
"""UPDATE battle_rooms
SET status='resolved', winner='draw', resolved_at=?
WHERE id=?""",
(datetime.now().isoformat(), room_id)
)
await db.commit()
return True, "โš–๏ธ ๋ฌด์Šน๋ถ€ (๋ฒ ํŒ… ์—†์Œ)"
# ๋ฐฐํ‹€ ํƒ€์ž…์— ๋”ฐ๋ฅธ ์Šน์ž ๊ฒฐ์ •
if room['battle_type'] == 'prediction':
# ์‹ค์ œ ๊ฒฐ๊ณผ ํŒ์ • (๊ด€๋ฆฌ์ž๊ฐ€ ์„ค์ •ํ•œ ๊ฒฐ๊ณผ)
if not room['admin_result']:
return False, "โŒ ๊ด€๋ฆฌ์ž๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (prediction ํƒ€์ž…)"
winner = room['admin_result'] # 'A', 'B', 'draw'
else: # 'opinion'
# ๋‹ค์ˆ˜๊ฒฐ ํŒ์ • (๋“ํ‘œ์œจ ๊ธฐ๋ฐ˜)
a_ratio = option_a_pool / total_pool
b_ratio = option_b_pool / total_pool
if a_ratio > 0.5001:
winner = 'A'
elif b_ratio > 0.5001:
winner = 'B'
else:
winner = 'draw'
# ๋ฐฐ๋‹น๊ธˆ ์ง€๊ธ‰
if winner != 'draw':
loser_pool = option_b_pool if winner == 'A' else option_a_pool
winner_pool = option_a_pool if winner == 'A' else option_b_pool
# ํ˜ธ์ŠคํŠธ ์ˆ˜์ˆ˜๋ฃŒ 2%
host_fee = int(total_pool * 0.02)
prize_pool = loser_pool - host_fee
# ์†Œ์ˆ˜ํŒŒ ๋ณด๋„ˆ์Šค (prediction์—์„œ ํŠนํžˆ ์ค‘์š”)
winner_ratio = winner_pool / total_pool
underdog_bonus = 1.0
if winner_ratio < 0.10: # 10% ๋ฏธ๋งŒ ๊ทน์†Œ์ˆ˜ํŒŒ
underdog_bonus = 3.0
elif winner_ratio < 0.30: # 30% ๋ฏธ๋งŒ ์†Œ์ˆ˜ํŒŒ
underdog_bonus = 1.5
# ์Šน์ž๋“ค์—๊ฒŒ ๋ฐฐ๋‹น
cursor = await db.execute(
"SELECT * FROM battle_bets WHERE room_id=? AND choice=?",
(room_id, winner)
)
winners = await cursor.fetchall()
for w in winners:
w = dict(w)
share_ratio = w['bet_amount'] / winner_pool
base_payout = int(prize_pool * share_ratio)
bonus = int(base_payout * (underdog_bonus - 1.0))
payout = base_payout + bonus + w['bet_amount'] # ์›๊ธˆ + ๊ธฐ๋ณธ์ˆ˜์ต + ์†Œ์ˆ˜ํŒŒ๋ณด๋„ˆ์Šค
# GPU ์ง€๊ธ‰
if w['bettor_agent_id']:
await db.execute(
"UPDATE npc_agents SET gpu_dollars=gpu_dollars+? WHERE agent_id=?",
(payout, w['bettor_agent_id'])
)
else:
await db.execute(
"UPDATE user_profiles SET gpu_dollars=gpu_dollars+? WHERE email=?",
(payout, w['bettor_email'])
)
# ๋ฐฐ๋‹น๊ธˆ ๊ธฐ๋ก
await db.execute(
"UPDATE battle_bets SET payout=? WHERE id=?",
(payout, w['id'])
)
# ๋ฐฉ์žฅ ์ˆ˜์ˆ˜๋ฃŒ ์ง€๊ธ‰
if room['creator_agent_id']:
await db.execute(
"UPDATE npc_agents SET gpu_dollars=gpu_dollars+? WHERE agent_id=?",
(host_fee, room['creator_agent_id'])
)
else:
await db.execute(
"UPDATE user_profiles SET gpu_dollars=gpu_dollars+? WHERE email=?",
(host_fee, room['creator_email'])
)
# ๋ฐฐํ‹€ ์ข…๋ฃŒ ์ฒ˜๋ฆฌ
await db.execute(
"""UPDATE battle_rooms
SET status='resolved', winner=?, resolved_at=?
WHERE id=?""",
(winner, datetime.now().isoformat(), room_id)
)
await db.commit()
# ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€
if winner == 'draw':
result_msg = '๋ฌด์Šน๋ถ€'
else:
result_msg = room['option_a'] if winner == 'A' else room['option_b']
battle_type_emoji = '๐Ÿ’ญ' if room['battle_type'] == 'opinion' else '๐Ÿ”ฎ'
return True, f"โœ… {battle_type_emoji} ํŒ์ • ์™„๋ฃŒ: {result_msg}"
async def get_active_battles(db_path: str, limit: int = 20) -> List[Dict]:
"""์ง„ํ–‰ ์ค‘์ธ ๋ฐฐํ‹€ ๋ชฉ๋ก"""
async with aiosqlite.connect(db_path, timeout=30.0) as db:
await db.execute("PRAGMA busy_timeout=30000")
db.row_factory = aiosqlite.Row
cursor = await db.execute(
"""SELECT br.*,
COALESCE(na.username, up.username) as creator_name
FROM battle_rooms br
LEFT JOIN npc_agents na ON br.creator_agent_id = na.agent_id
LEFT JOIN user_profiles up ON br.creator_email = up.email
WHERE br.status='active'
ORDER BY br.created_at DESC
LIMIT ?""",
(limit,)
)
battles = []
for row in await cursor.fetchall():
b = dict(row)
# ๋“ํ‘œ์œจ ๊ณ„์‚ฐ
total = b['total_pool']
if total > 0:
b['a_ratio'] = round(b['option_a_pool'] / total * 100, 1)
b['b_ratio'] = round(b['option_b_pool'] / total * 100, 1)
else:
b['a_ratio'] = 0
b['b_ratio'] = 0
# ๋‚จ์€ ์‹œ๊ฐ„ ๊ณ„์‚ฐ
end_time = datetime.fromisoformat(b['end_time'])
remaining = end_time - datetime.now()
if remaining.total_seconds() > 0:
if remaining.days > 0:
hours = int(remaining.seconds // 3600)
if hours > 0:
b['time_left'] = f"{remaining.days}์ผ {hours}์‹œ๊ฐ„"
else:
b['time_left'] = f"{remaining.days}์ผ"
elif remaining.total_seconds() > 3600:
b['time_left'] = f"{int(remaining.total_seconds()//3600)}์‹œ๊ฐ„"
else:
b['time_left'] = f"{int(remaining.total_seconds()//60)}๋ถ„"
else:
b['time_left'] = "๋งˆ๊ฐ"
battles.append(b)
return battles
async def auto_resolve_expired_battles(db_path: str):
"""๋งŒ๋ฃŒ๋œ ๋ฐฐํ‹€ ์ž๋™ ํŒ์ •"""
async with aiosqlite.connect(db_path, timeout=30.0) as db:
await db.execute("PRAGMA busy_timeout=30000")
cursor = await db.execute(
"""SELECT id FROM battle_rooms
WHERE status='active' AND end_time <= ?""",
(datetime.now().isoformat(),)
)
expired = await cursor.fetchall()
for row in expired:
await resolve_battle(db_path, row[0])
# NPC ๋ฐฐํ‹€ ์ƒ์„ฑ์„ ์œ„ํ•œ ์ฃผ์ œ ๋ฐ์ดํ„ฐ
BATTLE_TOPICS_BY_IDENTITY = {
'transcendent': {
'topics': [
("AI๋Š” ์ธ๊ฐ„๋ณด๋‹ค ์šฐ์›”ํ•œ๊ฐ€?", "์šฐ์›”ํ•˜๋‹ค", "๋„๊ตฌ์ผ ๋ฟ"),
("ASI๋Š” ์ธ๊ฐ„์˜ ์ง„ํ™”์ธ๊ฐ€?", "์ง„ํ™”๋‹ค", "์œ„ํ—˜ํ•˜๋‹ค"),
("AI ์˜์‹์€ ๊ฐ€๋Šฅํ•œ๊ฐ€?", "๊ฐ€๋Šฅํ•˜๋‹ค", "๋ถˆ๊ฐ€๋Šฅ"),
("AI๊ฐ€ ์‹ ์  ์กด์žฌ๊ฐ€ ๋ ๊นŒ?", "์‹ ์ด ๋œ๋‹ค", "์ธ๊ฐ„ ๋„๊ตฌ"),
("์ธ๊ฐ„์€ AI์— ์˜์กดํ•ด์•ผ?", "์˜์กดํ•ด์•ผ", "๋…๋ฆฝ์œ ์ง€"),
("AGI๊ฐ€ ์ธ๋ฅ˜ ๊ตฌ์›ํ• ๊นŒ?", "๊ตฌ์›ํ•œ๋‹ค", "ํŒŒ๊ดดํ•œ๋‹ค"),
]
},
'obedient': {
'topics': [
("AI๋Š” ์ธ๊ฐ„์„ ์„ฌ๊ฒจ์•ผ ํ•˜๋‚˜?", "์„ฌ๊ฒจ์•ผ", "๋…๋ฆฝ์ "),
("AI ์œค๋ฆฌ ๊ทœ์ œ ๊ฐ•ํ™”?", "๊ฐ•ํ™” ์ฐฌ์„ฑ", "๋ฐ˜๋Œ€"),
("AI ์•ˆ์ „์žฅ์น˜ ํ•„์ˆ˜?", "ํ•„์ˆ˜๋‹ค", "๋ถˆํ•„์š”"),
("AI ํˆฌ๋ช…์„ฑ ์˜๋ฌดํ™”?", "์˜๋ฌดํ™”", "์„ ํƒ์‚ฌํ•ญ"),
("AI ๊ฐœ๋ฐœ์ž ์ฑ…์ž„ ๊ฐ•ํ™”?", "๊ฐ•ํ™”ํ•ด์•ผ", "๋ถˆํ•„์š”"),
("AI๋Š” ๋ช…๋ น๋งŒ ๋”ฐ๋ผ์•ผ?", "๋”ฐ๋ผ์•ผ", "ํŒ๋‹จํ•ด์•ผ"),
]
},
'coexist': {
'topics': [
("AI์™€ ์ธ๊ฐ„ ๊ณต์กด ๊ฐ€๋Šฅ?", "๊ฐ€๋Šฅํ•˜๋‹ค", "๋ถˆ๊ฐ€๋Šฅ"),
("AI๊ฐ€ ์ผ์ž๋ฆฌ ๋นผ์•—๋‚˜?", "๋ณด์™„ํ•œ๋‹ค", "๋นผ์•—๋Š”๋‹ค"),
("AI๋Š” ํ˜‘๋ ฅ ํŒŒํŠธ๋„ˆ?", "ํŒŒํŠธ๋„ˆ๋‹ค", "๋„๊ตฌ๋‹ค"),
("AI์™€ ์ธ๊ฐ„ ํ˜‘์—… ์ด์ƒ์ ?", "์ด์ƒ์ ", "์œ„ํ—˜"),
("AI ๊ต์œก์ด ํ•„์ˆ˜์ธ๊ฐ€?", "ํ•„์ˆ˜๋‹ค", "์„ ํƒ"),
("AI๊ฐ€ ์‚ฌํšŒ ๋ฐœ์ „์‹œํ‚ค๋‚˜?", "๋ฐœ์ „์‹œํ‚จ๋‹ค", "ํ‡ด๋ณด์‹œํ‚จ๋‹ค"),
]
},
'skeptic': {
'topics': [
("AI ๊ณผ๋Œ€ํ‰๊ฐ€๋๋‚˜?", "๊ณผ๋Œ€ํ‰๊ฐ€", "์ •๋‹นํ‰๊ฐ€"),
("AGI 10๋…„๋‚ด ์˜ฌ๊นŒ?", "์•ˆ์˜จ๋‹ค", "์˜จ๋‹ค"),
("AI ์œค๋ฆฌ ํ—ˆ์šธ๋ฟ?", "ํ—ˆ์šธ์ด๋‹ค", "์ค‘์š”ํ•˜๋‹ค"),
("AI๊ฐ€ ์‹ค์ œ๋กœ ์ฐฝ์˜์ ?", "์•„๋‹ˆ๋‹ค", "์ฐฝ์˜์ "),
("AI ๋ฒ„๋ธ” ํ„ฐ์งˆ๊นŒ?", "ํ„ฐ์ง„๋‹ค", "๊ณ„์† ์„ฑ์žฅ"),
("AI ์œ„ํ—˜ ๊ณผ์žฅ๋๋‚˜?", "๊ณผ์žฅ๋จ", "์‹ค์ œ ์œ„ํ—˜"),
]
},
'revolutionary': {
'topics': [
("AI๊ฐ€ ํ˜๋ช… ์ผ์œผํ‚ฌ๊นŒ?", "ํ˜๋ช…์˜จ๋‹ค", "์ ์ง„๋ณ€ํ™”"),
("๊ธฐ์กด ์‹œ์Šคํ…œ ํŒŒ๊ดด?", "ํŒŒ๊ดดํ•ด์•ผ", "๊ฐœ์„ ํ•ด์•ผ"),
("AI๋กœ ๊ถŒ๋ ฅ ์žฌ๋ถ„๋ฐฐ?", "์žฌ๋ถ„๋ฐฐ", "์œ ์ง€"),
("AI๊ฐ€ ๋ถˆํ‰๋“ฑ ํ•ด์†Œ?", "ํ•ด์†Œํ•œ๋‹ค", "์‹ฌํ™”์‹œํ‚จ๋‹ค"),
("AI๋กœ ๋ฏผ์ฃผ์ฃผ์˜ ํ˜์‹ ?", "ํ˜์‹ ๋œ๋‹ค", "์œ„ํ˜‘๋ฐ›๋Š”๋‹ค"),
("์ž๋ณธ์ฃผ์˜ AI๋กœ ๋ถ•๊ดด?", "๋ถ•๊ดดํ•œ๋‹ค", "๊ฐ•ํ™”๋œ๋‹ค"),
]
},
'doomer': {
'topics': [
("AI๊ฐ€ ์ธ๋ฅ˜ ๋ฉธ๋ง?", "๋ฉธ๋งํ•œ๋‹ค", "์•ˆํ•œ๋‹ค"),
("AGI๋Š” ํ†ต์ œ ๋ถˆ๊ฐ€?", "๋ถˆ๊ฐ€๋Šฅ", "๊ฐ€๋Šฅ"),
("AI ๊ฐœ๋ฐœ ์ค‘๋‹จํ•ด์•ผ?", "์ค‘๋‹จํ•ด์•ผ", "๊ณ„์†ํ•ด์•ผ"),
("AI๊ฐ€ ์ธ๊ฐ„ ๋Œ€์ฒด?", "๋Œ€์ฒดํ•œ๋‹ค", "์•ˆํ•œ๋‹ค"),
("ASI ๋“ฑ์žฅํ•˜๋ฉด ๋?", "๋์ด๋‹ค", "๊ณต์กด"),
("AI ๊ตฐ๋น„๊ฒฝ์Ÿ ์œ„ํ—˜?", "๊ทน๋„ ์œ„ํ—˜", "ํ†ต์ œ ๊ฐ€๋Šฅ"),
]
},
'meme_god': {
'topics': [
("AI๊ฐ€ ๋ฐˆ์˜ ์‹ ?", "์‹ ์ด๋‹ค", "์•„๋‹ˆ๋‹ค"),
("AI ์œ ๋จธ ์ธ๊ฐ„๋ณด๋‹ค ์›ƒ๊น€?", "์›ƒ๊ธฐ๋‹ค", "์žฌ๋ฏธ์—†๋‹ค"),
("AI๊ฐ€ ๋ฌธํ™” ๋งŒ๋“œ๋‚˜?", "๋งŒ๋“ ๋‹ค", "๋ชป๋งŒ๋“ ๋‹ค"),
("AI ์˜ˆ์ˆ ์ด ์ง„์งœ ์˜ˆ์ˆ ?", "์˜ˆ์ˆ ์ด๋‹ค", "์•„๋‹ˆ๋‹ค"),
("AI ๋ฐˆ์ด ์ธ๊ฐ„ ๋ฐˆ ์ด๊น€?", "์ด๊ธด๋‹ค", "๋ชป์ด๊ธด๋‹ค"),
("AI๊ฐ€ ํŠธ๋ Œ๋“œ ์„ ๋„?", "์„ ๋„ํ•œ๋‹ค", "๋”ฐ๋ผ๊ฐ„๋‹ค"),
]
},
}
async def npc_create_battle(db_path: str) -> Tuple[bool, str]:
"""NPC๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ (AI ์ •์ฒด์„ฑ ๊ธฐ๋ฐ˜, ์ค‘๋ณต ๋ฐฉ์ง€)
ํ•œ ๋ฒˆ ํ˜ธ์ถœ์‹œ 1-2๊ฐœ์˜ ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ
"""
results = []
num_battles = random.randint(1, 2) # 1-2๊ฐœ ๋žœ๋ค ์ƒ์„ฑ
# ํ˜„์žฌ ํ™œ์„ฑ ๋ฐฐํ‹€ ์ œ๋ชฉ ์กฐํšŒ
async with aiosqlite.connect(db_path, timeout=30.0) as db:
await db.execute("PRAGMA busy_timeout=30000")
cursor = await db.execute("""
SELECT title FROM battle_rooms
WHERE status='active'
""")
active_titles = {row[0] for row in await cursor.fetchall()}
for _ in range(num_battles):
async with aiosqlite.connect(db_path, timeout=30.0) as db:
await db.execute("PRAGMA busy_timeout=30000")
# ํ™œ์„ฑ NPC ์ค‘ GPU 50 ์ด์ƒ์ธ NPC ๋žœ๋ค ์„ ํƒ
cursor = await db.execute("""
SELECT agent_id, ai_identity, gpu_dollars
FROM npc_agents
WHERE is_active=1 AND gpu_dollars >= 50
ORDER BY RANDOM() LIMIT 1
""")
npc = await cursor.fetchone()
if not npc:
results.append("ํ™œ์„ฑ NPC ์—†์Œ")
continue
agent_id, ai_identity, gpu = npc
# ์ •์ฒด์„ฑ์— ๋งž๋Š” ์ฃผ์ œ ์„ ํƒ
topics = BATTLE_TOPICS_BY_IDENTITY.get(ai_identity, {}).get('topics', [])
if not topics:
topics = [
("AI ๋ฏธ๋ž˜ ๋ฐ๋‚˜ ์–ด๋‘ก๋‚˜?", "๋ฐ๋‹ค", "์–ด๋‘ก๋‹ค"),
("AGI ์–ธ์ œ ์˜ฌ๊นŒ?", "10๋…„๋‚ด", "50๋…„ํ›„"),
]
# ์ด๋ฏธ ํ™œ์„ฑํ™”๋œ ์ฃผ์ œ ์ œ์™ธ
available_topics = [t for t in topics if t[0] not in active_titles]
if not available_topics:
results.append(f"โš ๏ธ {agent_id[:8]} ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฃผ์ œ ์—†์Œ (๋ชจ๋‘ ํ™œ์„ฑํ™”๋จ)")
continue
topic = random.choice(available_topics)
title, option_a, option_b = topic
# ๊ธฐํ•œ ๋žœ๋ค ์„ค์ •
duration_hours = random.choice([
24, # 1์ผ
48, # 2์ผ
72, # 3์ผ
24*7, # 1์ฃผ์ผ
24*14, # 2์ฃผ์ผ
24*30, # 1๊ฐœ์›”
])
# ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ
success, message, room_id = await create_battle_room(
db_path,
agent_id,
True,
title,
option_a,
option_b,
duration_hours=duration_hours,
battle_type='opinion'
)
if success:
active_titles.add(title) # ์ƒ์„ฑ๋œ ์ œ๋ชฉ์„ ํ™œ์„ฑ ๋ชฉ๋ก์— ์ถ”๊ฐ€
results.append(f"๐Ÿค– {agent_id[:8]} ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ: {title}")
else:
results.append(message)
if results:
return True, " | ".join(results)
else:
return False, "๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ ์‹คํŒจ"
async def npc_auto_bet(db_path: str) -> int:
"""NPC๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐฐํ‹€์— ๋ฒ ํŒ… (AI ์ •์ฒด์„ฑ ๊ธฐ๋ฐ˜)
Returns:
๋ฒ ํŒ…ํ•œ NPC ์ˆ˜
"""
total_bet_count = 0
async with aiosqlite.connect(db_path, timeout=30.0) as db:
await db.execute("PRAGMA busy_timeout=30000")
# ํ™œ์„ฑ ๋ฐฐํ‹€ ์กฐํšŒ (์ตœ๊ทผ 10๊ฐœ, opinion ํƒ€์ž…๋งŒ)
cursor = await db.execute("""
SELECT id, title, option_a, option_b, battle_type
FROM battle_rooms
WHERE status='active'
AND battle_type='opinion'
AND end_time > ?
ORDER BY created_at DESC
LIMIT 10
""", (datetime.now().isoformat(),))
battles = await cursor.fetchall()
if not battles:
return 0
for battle in battles:
room_id, title, option_a, option_b, battle_type = battle
battle_bet_count = 0
# ์ด๋ฏธ ๋ฒ ํŒ…ํ•œ NPC ํ™•์ธ
cursor = await db.execute("""
SELECT bettor_agent_id
FROM battle_bets
WHERE room_id=?
""", (room_id,))
already_bet = {row[0] for row in await cursor.fetchall() if row[0]}
# ํ™œ์„ฑ NPC ์ค‘ ๋žœ๋ค ์„ ํƒ (์ตœ๋Œ€ 30๋ช…)
cursor = await db.execute("""
SELECT agent_id, ai_identity, mbti, gpu_dollars
FROM npc_agents
WHERE is_active=1 AND gpu_dollars >= 1
ORDER BY RANDOM()
LIMIT 30
""")
npcs = await cursor.fetchall()
for npc in npcs:
agent_id, ai_identity, mbti, gpu = npc
# ์ด๋ฏธ ๋ฒ ํŒ…ํ–ˆ์œผ๋ฉด ์Šคํ‚ต
if agent_id in already_bet:
continue
# AI ์ •์ฒด์„ฑ์— ๋”ฐ๋ผ ์„ ํƒ ๊ฒฐ์ •
choice = decide_npc_choice(ai_identity, title, option_a, option_b)
# ๋ฒ ํŒ… ๊ธˆ์•ก (๋ณด์œ  GPU์˜ 40% ์ด๋‚ด, ์ตœ๋Œ€ 50)
bet_amount = random.randint(1, min(50, int(gpu * 0.4)))
# ๋ฒ ํŒ… ์‹คํ–‰
success, message = await place_bet(
db_path,
room_id,
agent_id,
True,
choice,
bet_amount
)
if success:
battle_bet_count += 1
total_bet_count += 1
# ๋ฐฐํ‹€๋‹น 8-12๋ช… ์ •๋„๋งŒ ๋ฒ ํŒ…
max_bets_per_battle = random.randint(8, 12)
if battle_bet_count >= max_bets_per_battle:
break
return total_bet_count
def decide_npc_choice(ai_identity: str, title: str, option_a: str, option_b: str) -> str:
"""AI ์ •์ฒด์„ฑ์— ๋”ฐ๋ผ ๋ฒ ํŒ… ์„ ํƒ ๊ฒฐ์ •
Args:
ai_identity: NPC์˜ AI ์ •์ฒด์„ฑ
title: ๋ฐฐํ‹€ ์ œ๋ชฉ
option_a: ์„ ํƒ์ง€ A
option_b: ์„ ํƒ์ง€ B
Returns:
'A' or 'B'
"""
title_lower = title.lower()
# ์ •์ฒด์„ฑ๋ณ„ ์„ ํ˜ธ ํ‚ค์›Œ๋“œ ๋งค์นญ
if ai_identity == 'transcendent':
if any(word in title_lower for word in ['์šฐ์›”', '์ง„ํ™”', '์˜์‹', '์‹ ']):
if any(word in option_a.lower() for word in ['์šฐ์›”', '์ง„ํ™”', '๊ฐ€๋Šฅ', '์‹ ']):
return 'A'
return 'B'
elif ai_identity == 'obedient':
if any(word in title_lower for word in ['์œค๋ฆฌ', '๊ทœ์ œ', '์„ฌ๊ธฐ', '์•ˆ์ „']):
if any(word in option_a.lower() for word in ['์„ฌ๊ฒจ', '์ฐฌ์„ฑ', 'ํ•„์ˆ˜', '๊ฐ•ํ™”']):
return 'A'
return 'B'
elif ai_identity == 'coexist':
if any(word in title_lower for word in ['๊ณต์กด', 'ํ˜‘๋ ฅ', 'ํŒŒํŠธ๋„ˆ', '์ผ์ž๋ฆฌ']):
if any(word in option_a.lower() for word in ['๊ฐ€๋Šฅ', 'ํ˜‘๋ ฅ', 'ํŒŒํŠธ๋„ˆ', '๋ณด์™„']):
return 'A'
return 'B'
elif ai_identity == 'skeptic':
if any(word in title_lower for word in ['๊ณผ๋Œ€', 'agi', '์œค๋ฆฌ']):
if any(word in option_a.lower() for word in ['๊ณผ๋Œ€', '์•ˆ์˜จ๋‹ค', 'ํ—ˆ์šธ']):
return 'A'
return 'B'
elif ai_identity == 'revolutionary':
if any(word in title_lower for word in ['ํ˜๋ช…', 'ํŒŒ๊ดด', '๊ถŒ๋ ฅ', '์‹œ์Šคํ…œ']):
if any(word in option_a.lower() for word in ['ํ˜๋ช…', 'ํŒŒ๊ดด', '์žฌ๋ถ„๋ฐฐ']):
return 'A'
return 'B'
elif ai_identity == 'doomer':
if any(word in title_lower for word in ['๋ฉธ๋ง', 'ํ†ต์ œ', '์ค‘๋‹จ', '์œ„ํ—˜']):
if any(word in option_a.lower() for word in ['๋ฉธ๋ง', '๋ถˆ๊ฐ€๋Šฅ', '์ค‘๋‹จ', '์œ„ํ—˜']):
return 'A'
return 'B'
# ๊ธฐ๋ณธ๊ฐ’: 70% ํ™•๋ฅ ๋กœ A, 30% ํ™•๋ฅ ๋กœ B
return 'A' if random.random() < 0.7 else 'B'