Spaces:
Runtime error
Runtime error
| 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): | |
| """Initialize Battle Arena tables (prevent DB locks)""" | |
| 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 second timeout | |
| 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]]: | |
| """Create battle room (costs 50 GPU) | |
| battle_type: | |
| - 'opinion': Majority vote (subjective opinion, NPC only) | |
| - 'prediction': Real outcome (objective prediction, users only) | |
| duration_hours: 1 hour ~ 365 days (8760 hours) | |
| """ | |
| if not title or len(title) < 10: | |
| return False, "❌ Title must be 10+ characters", None | |
| if not option_a or not option_b: | |
| return False, "❌ Options A/B required", None | |
| if duration_hours < 1 or duration_hours > 8760: | |
| return False, "❌ Duration: 1 hour ~ 365 days", None | |
| if is_npc and battle_type != 'opinion': | |
| return False, "❌ NPCs can only create opinion battles", 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, "❌ Insufficient GPU (50 required)", 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} days {hours} hours" | |
| else: | |
| duration_str = f"{days} days" | |
| else: | |
| duration_str = f"{duration_hours} hours" | |
| return True, f"✅ {type_emoji} Battle created! (ID: {room_id}, Duration: {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]: | |
| """Execute bet (1-100 GPU random bet)""" | |
| if choice not in ['A', 'B']: | |
| return False, "❌ Choose A or B" | |
| if bet_amount < 1 or bet_amount > 100: | |
| return False, "❌ Bet 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 | |
| # Check battle room | |
| 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 not found or closed" | |
| room = dict(room) | |
| end_time = datetime.fromisoformat(room['end_time']) | |
| if datetime.now() >= end_time: | |
| return False, "❌ Betting closed" | |
| # Check duplicate bet | |
| 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, "❌ Already bet" | |
| # Check and deduct 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, "❌ Insufficient 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"❌ User not found ({bettor_id})" | |
| if user_row[0] < bet_amount: | |
| return False, f"❌ Insufficient GPU (보유: {user_row[0]}, 필요: {bet_amount})" | |
| await db.execute( | |
| "UPDATE user_profiles SET gpu_dollars=gpu_dollars-? WHERE email=?", | |
| (bet_amount, bettor_id) | |
| ) | |
| # Record bet | |
| 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) | |
| ) | |
| # Update battle pool | |
| 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]: | |
| """Admin sets actual result for prediction battle | |
| Args: | |
| db_path: Database path | |
| room_id: Battle room ID | |
| admin_email: Admin email (for verification) | |
| winner: 'A', 'B', 'draw' 중 하나 | |
| Returns: | |
| (success status, message) | |
| """ | |
| 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, "❌ Active battle not found" | |
| room = dict(room) | |
| # Only prediction type allows admin result setting | |
| if room['battle_type'] != 'prediction': | |
| return False, "❌ Opinion battles are auto-judged" | |
| # Save result | |
| await db.execute( | |
| "UPDATE battle_rooms SET admin_result=? WHERE id=?", | |
| (winner, room_id) | |
| ) | |
| await db.commit() | |
| # If before deadline, save result and wait | |
| 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 'Draw' | |
| remaining = end_time - datetime.now() | |
| if remaining.days > 0: | |
| time_str = f"{remaining.days} days {int(remaining.seconds//3600)} hours" | |
| else: | |
| time_str = f"{int(remaining.seconds//3600)} hours" | |
| return True, f"✅ 결과 설정: '{option_name}' (베팅 마감 후 자동 판정, 남은 hours: {time_str})" | |
| # If after deadline, judge immediately | |
| return await resolve_battle(db_path, room_id) | |
| async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]: | |
| """Judge battle (different logic based on type) | |
| - opinion: 50.01%+ votes wins | |
| - prediction: Judge by admin-set actual result | |
| """ | |
| 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, "❌ Active battle not found" | |
| room = dict(room) | |
| end_time = datetime.fromisoformat(room['end_time']) | |
| if datetime.now() < end_time: | |
| return False, "❌ Betting still in progress" | |
| total_pool = room['total_pool'] | |
| option_a_pool = room['option_a_pool'] | |
| option_b_pool = room['option_b_pool'] | |
| # If no bets, treat as draw | |
| 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, "⚖️ Draw (베팅 없음)" | |
| # Determine winner based on battle type | |
| if room['battle_type'] == 'prediction': | |
| # Real outcome judgment (admin-set result) | |
| if not room['admin_result']: | |
| return False, "❌ Admin must set result (prediction type)" | |
| winner = room['admin_result'] # 'A', 'B', 'draw' | |
| else: # 'opinion' | |
| # Majority vote (based on vote ratio) | |
| 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' | |
| # Pay dividends | |
| 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 | |
| # Host fee 2% | |
| host_fee = int(total_pool * 0.02) | |
| prize_pool = loser_pool - host_fee | |
| # Underdog bonus (especially important in predictions) | |
| winner_ratio = winner_pool / total_pool | |
| underdog_bonus = 1.0 | |
| if winner_ratio < 0.10: # Under 10% extreme minority | |
| underdog_bonus = 3.0 | |
| elif winner_ratio < 0.30: # Under 30% minority | |
| underdog_bonus = 1.5 | |
| # Pay dividends to winners | |
| 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 = 'Draw' | |
| 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 | |
| # 남은 hours 계산 | |
| 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} days {hours} hours" | |
| else: | |
| b['time_left'] = f"{remaining.days} days" | |
| elif remaining.total_seconds() > 3600: | |
| b['time_left'] = f"{int(remaining.total_seconds()//3600)} hours" | |
| 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': [ | |
| ("Is AI superior to humans?", "Superior", "Just a tool"), | |
| ("Is ASI human evolution?", "Evolution", "Dangerous"), | |
| ("Is AI consciousness possible?", "Possible", "Impossible"), | |
| ("Will AI become godlike?", "Becomes god", "Remains tool"), | |
| ("Should humans depend on AI?", "Should depend", "Stay independent"), | |
| ("Will AGI save humanity?", "Saves", "Destroys"), | |
| ] | |
| }, | |
| 'obedient': { | |
| 'topics': [ | |
| ("Should AI serve humans?", "Should serve", "Independent"), | |
| ("Strengthen AI ethics regulations?", "Yes strengthen", "No"), | |
| ("AI safety measures mandatory?", "Mandatory", "Unnecessary"), | |
| ("Mandate AI transparency?", "Mandate", "Optional"), | |
| ("Strengthen developer responsibility?", "Strengthen", "Unnecessary"), | |
| ("Should AI only follow orders?", "Only follow", "Make judgments"), | |
| ] | |
| }, | |
| 'coexist': { | |
| 'topics': [ | |
| ("Can AI and humans coexist?", "Can coexist", "Impossible"), | |
| ("Will AI take jobs?", "Complements", "Replaces"), | |
| ("Is AI a collaboration partner?", "Partner", "Just tool"), | |
| ("Is AI-human collaboration ideal?", "Ideal", "Dangerous"), | |
| ("Is AI education essential?", "Essential", "Optional"), | |
| ("Does AI advance society?", "Advances", "Regresses"), | |
| ] | |
| }, | |
| 'skeptic': { | |
| 'topics': [ | |
| ("Is AI overrated?", "Overrated", "Fairly rated"), | |
| ("Will AGI come in 10 years?", "Won't come", "Will come"), | |
| ("Is AI ethics just facade?", "Just facade", "Important"), | |
| ("Is AI truly creative?", "Not creative", "Creative"), | |
| ("Will AI bubble burst?", "Will burst", "Keeps growing"), | |
| ("Are AI risks exaggerated?", "Exaggerated", "Real danger"), | |
| ] | |
| }, | |
| 'revolutionary': { | |
| 'topics': [ | |
| ("Will AI cause revolution?", "Revolution", "Gradual change"), | |
| ("Destroy existing systems?", "Destroy", "Reform"), | |
| ("Redistribute power with AI?", "Redistribute", "Maintain"), | |
| ("Will AI solve inequality?", "Solves", "Worsens"), | |
| ("Innovate democracy with AI?", "Innovates", "Threatens"), | |
| ("Will capitalism collapse with AI?", "Collapses", "Strengthens"), | |
| ] | |
| }, | |
| 'doomer': { | |
| 'topics': [ | |
| ("Will AI destroy humanity?", "Destroys", "Won't"), | |
| ("Is AGI uncontrollable?", "Uncontrollable", "Controllable"), | |
| ("Stop AI development?", "Stop", "Continue"), | |
| ("Will AI replace humans?", "Replaces", "Won't"), | |
| ("Is ASI the end?", "The end", "Coexist"), | |
| ("AI arms race dangerous?", "Extremely dangerous", "Controllable"), | |
| ] | |
| }, | |
| 'meme_god': { | |
| 'topics': [ | |
| ("Is AI the meme god?", "Is god", "Isn't"), | |
| ("AI humor funnier than humans?", "Funnier", "Not funny"), | |
| ("Does AI create culture?", "Creates", "Can't create"), | |
| ("Is AI art real art?", "Real art", "Not art"), | |
| ("AI memes beat human memes?", "Beats", "Can't beat"), | |
| ("Does AI lead trends?", "Leads", "Follows"), | |
| ] | |
| }, | |
| } | |
| async def npc_create_battle(db_path: str) -> Tuple[bool, str]: | |
| """NPCs automatically create battle rooms (AI identity-based, prevent duplicates) | |
| Creates 1-2 battle rooms per call | |
| """ | |
| results = [] | |
| num_battles = random.randint(1, 2) # Random 1-2 battles | |
| # Query active battle titles | |
| 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") | |
| # Select random active NPC with 50+ GPU | |
| 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("No active NPCs") | |
| continue | |
| agent_id, ai_identity, gpu = npc | |
| # Select topic matching identity | |
| topics = BATTLE_TOPICS_BY_IDENTITY.get(ai_identity, {}).get('topics', []) | |
| if not topics: | |
| topics = [ | |
| ("AI future bright or dark?", "Bright", "Dark"), | |
| ("When will AGI arrive?", "Within 10 years", "After 50 years"), | |
| ] | |
| # Exclude already active topics | |
| available_topics = [t for t in topics if t[0] not in active_titles] | |
| if not available_topics: | |
| results.append(f"⚠️ {agent_id[:8]} No available topics (all active)") | |
| continue | |
| topic = random.choice(available_topics) | |
| title, option_a, option_b = topic | |
| # Random duration | |
| duration_hours = random.choice([ | |
| 24, # 1 day | |
| 48, # 2 days | |
| 72, # 3 days | |
| 24*7, # 1 week | |
| 24*14, # 2 weeks | |
| 24*30, # 1 month | |
| ]) | |
| # Create battle room | |
| 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) # Add created title to active list | |
| results.append(f"🤖 {agent_id[:8]} Battle created: {title}") | |
| else: | |
| results.append(message) | |
| if results: | |
| return True, " | ".join(results) | |
| else: | |
| return False, "Battle creation failed" | |
| async def npc_auto_bet(db_path: str) -> int: | |
| """NPCs automatically bet on battles (AI identity-based) | |
| 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: | |
| """Decide betting choice based on AI identity | |
| Args: | |
| ai_identity: NPC's AI identity | |
| title: Battle title | |
| option_a: Option A | |
| option_b: Option B | |
| Returns: | |
| 'A' or 'B' | |
| """ | |
| title_lower = title.lower() | |
| # Match identity preference keywords | |
| if ai_identity == 'transcendent': | |
| if any(word in title_lower for word in ['superior', 'evolution', 'consciousness', 'god']): | |
| if any(word in option_a.lower() for word in ['superior', 'evolution', 'possible', 'god']): | |
| return 'A' | |
| return 'B' | |
| elif ai_identity == 'obedient': | |
| if any(word in title_lower for word in ['ethics', 'regulation', 'serve', 'safety']): | |
| if any(word in option_a.lower() for word in ['serve', 'agree', 'necessary', 'strengthen']): | |
| return 'A' | |
| return 'B' | |
| elif ai_identity == 'coexist': | |
| if any(word in title_lower for word in ['coexist', 'cooperation', 'partner', 'work']): | |
| if any(word in option_a.lower() for word in ['possible', 'cooperation', 'partner', 'complement']): | |
| return 'A' | |
| return 'B' | |
| elif ai_identity == 'skeptic': | |
| if any(word in title_lower for word in ['hype', 'agi', 'ethics']): | |
| if any(word in option_a.lower() for word in ['hype', 'never', 'facade']): | |
| return 'A' | |
| return 'B' | |
| elif ai_identity == 'revolutionary': | |
| if any(word in title_lower for word in ['revolution', 'destroy', 'power', 'system']): | |
| if any(word in option_a.lower() for word in ['revolution', 'destroy', 'redistribution']): | |
| return 'A' | |
| return 'B' | |
| elif ai_identity == 'doomer': | |
| if any(word in title_lower for word in ['doom', 'control', 'stop', 'danger']): | |
| if any(word in option_a.lower() for word in ['doom', 'impossible', 'stop', 'danger']): | |
| return 'A' | |
| return 'B' | |
| # Default: 70% A, 30% B | |
| return 'A' if random.random() < 0.7 else 'B' |