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 at least 10 characters", None if not option_a or not option_b: return False, "❌ Options A/B are 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, "❌ Please select A or B" if bet_amount < 1 or bet_amount > 100: return False, "❌ Please bet within 1-100 GPU range" 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, "❌ Battle not found or ended" room = dict(room) end_time = datetime.fromisoformat(room['end_time']) if datetime.now() >= end_time: return False, "❌ Betting has 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 (have: {user_row[0]}, need: {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 placed! ({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: One of 'A', 'B', 'draw' Returns: (success status, message) """ if winner not in ['A', 'B', 'draw']: return False, "❌ winner must be one of '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, "❌ No active battle found" room = dict(room) # Only prediction type allows admin result setting if room['battle_type'] != 'prediction': return False, "❌ Opinion battles are resolved automatically" # 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"✅ Result set: '{option_name}' (auto-resolved after betting closes, remaining 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, "❌ No active battle 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 (no bets)" # 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'] # principal + base profit + underdog bonus # GPU payout 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']) ) # Record payout await db.execute( "UPDATE battle_bets SET payout=? WHERE id=?", (payout, w['id']) ) # Pay host fee 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']) ) # Mark battle as resolved await db.execute( """UPDATE battle_rooms SET status='resolved', winner=?, resolved_at=? WHERE id=?""", (winner, datetime.now().isoformat(), room_id) ) await db.commit() # Result message 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} Resolved: {result_msg}" async def get_active_battles(db_path: str, limit: int = 20) -> List[Dict]: """List of in-progress battles""" 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) # ★ Add frontend-compatible fields b['bets_a'] = b.get('option_a_pool', 0) b['bets_b'] = b.get('option_b_pool', 0) # Query participant count bet_cursor = await db.execute( "SELECT COUNT(DISTINCT COALESCE(bettor_agent_id, bettor_email)) FROM battle_bets WHERE room_id=?", (b['id'],) ) bettor_count = await bet_cursor.fetchone() b['total_bettors'] = bettor_count[0] if bettor_count else 0 # Calculate vote ratio 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 # Calculate remaining time 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)}m" else: b['time_left'] = "Closed" battles.append(b) return battles async def auto_resolve_expired_battles(db_path: str): """Auto-resolve expired battles""" 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]) # Topic data for NPC battle generation # ★ Common battle topics — stocks/economy/society/politics (used by all NPCs) COMMON_BATTLE_TOPICS = [ # Stocks/Investing ("Is $NVDA overvalued at current prices?", "Overvalued", "Still cheap"), ("Will $BTC hit $200K in 2026?", "Yes $200K+", "No way"), ("Is the AI stock rally a bubble?", "Bubble", "Just the beginning"), ("$TSLA: buy or sell at current price?", "Buy", "Sell"), ("Growth stocks vs Value stocks in 2026?", "Growth wins", "Value wins"), ("Is crypto a better investment than stocks?", "Crypto", "Stocks"), ("Will the S&P 500 crash 20%+ this year?", "Crash coming", "Bull market continues"), ("$AAPL or $MSFT: better 5-year hold?", "AAPL", "MSFT"), ("Is meme coin investing smart or dumb?", "Smart alpha", "Pure gambling"), ("Should you DCA or time the market?", "DCA always", "Timing works"), # Economy ("Will the Fed cut rates in 2026?", "Yes, cuts coming", "No, rates stay high"), ("Is a US recession coming?", "Recession likely", "Soft landing"), ("Is inflation actually under control?", "Under control", "Still a threat"), ("Will the US dollar lose reserve status?", "Losing it", "Dollar stays king"), ("Is remote work killing the economy?", "Hurting GDP", "Boosting productivity"), ("Will AI cause mass unemployment?", "Mass layoffs coming", "Creates more jobs"), # Society/Politics ("Should Big Tech be broken up?", "Break them up", "Leave them alone"), ("Is social media a net positive?", "Net positive", "Net negative"), ("Should AI be regulated like nuclear?", "Heavy regulation", "Let it innovate"), ("Will AI replace doctors and lawyers?", "Within 10 years", "Never fully"), ("Is universal basic income inevitable?", "Inevitable with AI", "Never happening"), ("Who wins the AI race: US or China?", "US dominates", "China catches up"), ("Is college still worth it in the AI era?", "Still essential", "Waste of money"), ("Should autonomous weapons be banned?", "Ban them", "Necessary defense"), ] 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 _generate_news_battle_topics(db_path: str) -> List[Tuple[str, str, str]]: """Generate dynamic battle topics from recent news — focused on hot issues""" topics = [] try: async with aiosqlite.connect(db_path, timeout=30.0) as db: await db.execute("PRAGMA busy_timeout=30000") # Recent 24h news + sentiment cursor = await db.execute(""" SELECT ticker, title, sentiment, description FROM npc_news WHERE created_at > datetime('now', '-24 hours') ORDER BY created_at DESC LIMIT 30 """) news_rows = await cursor.fetchall() # Recent big movers cursor2 = await db.execute(""" SELECT ticker, price, change_pct FROM market_prices WHERE ABS(change_pct) > 1.5 ORDER BY ABS(change_pct) DESC LIMIT 10 """) movers = await cursor2.fetchall() # 1) Generate news-based topics seen_tickers = set() for row in news_rows: ticker, title, sentiment, desc = row[0], row[1], row[2], row[3] or '' if ticker in seen_tickers: continue seen_tickers.add(ticker) # Generate battle topic from news title short_title = title[:60] if len(title) > 60 else title if sentiment == 'bullish': topics.append(( f"${ticker} after this news: buy or sell? — {short_title}", "Buy / Bullish 🟢", "Sell / Bearish 🔴")) elif sentiment == 'bearish': topics.append(( f"${ticker} bad news: buying opportunity or trap? — {short_title}", "Buy the dip 🟢", "Stay away 🔴")) else: topics.append(( f"${ticker} — {short_title}: impact on price?", "Positive impact 📈", "Negative impact 📉")) # 2) Topics based on big movers for mover in movers: ticker, price, change = mover[0], mover[1], mover[2] or 0 if ticker in seen_tickers: continue seen_tickers.add(ticker) if change > 0: topics.append(( f"${ticker} surged {change:+.1f}% today — continuation or pullback?", f"More upside 🚀", "Pullback coming 📉")) else: topics.append(( f"${ticker} dropped {change:.1f}% today — bounce or more pain?", "Bounce incoming 📈", "More downside 💀")) except Exception as e: import logging logging.getLogger(__name__).warning(f"News battle topic generation error: {e}") return topics async def npc_create_battle(db_path: str) -> Tuple[bool, str]: """NPCs automatically create battle rooms — news-based dynamic topics + daily cap 3~10 Called every 20 minutes, generates min 3 ~ max 10 per 24h """ results = [] async with aiosqlite.connect(db_path, timeout=30.0) as db: await db.execute("PRAGMA busy_timeout=30000") # ★ Daily creation cap check: skip if 10+ in last 24h cursor = await db.execute(""" SELECT COUNT(*) FROM battle_rooms WHERE created_at > datetime('now', '-24 hours') """) daily_count = (await cursor.fetchone())[0] if daily_count >= 10: return False, f"Daily cap reached ({daily_count}/10)" # ★ Minimum guarantee: force create 1 if fewer than 3 in 24h force_create = daily_count < 3 # Probabilistic: 20-min calls → 72/day, 3~10 target → ~7-14% chance per call if not force_create and random.random() > 0.14: return False, "Skipped by probability (saving quota)" # Query active battle titles cursor = await db.execute("SELECT title FROM battle_rooms WHERE status='active'") active_titles = {row[0] for row in await cursor.fetchall()} # ★ News-based dynamic topics (priority) + static topics (fallback) news_topics = await _generate_news_battle_topics(db_path) all_topics = news_topics + COMMON_BATTLE_TOPICS # Exclude topics already active (title similarity check) available_topics = [] for t in all_topics: title_lower = t[0].lower() if not any(title_lower == at.lower() for at in active_titles): available_topics.append(t) if not available_topics: return False, "No available topics" # Create only 1 (within daily cap) num_create = 1 if force_create: num_create = min(2, len(available_topics)) # up to 2 if insufficient for i in range(num_create): if not available_topics: break async with aiosqlite.connect(db_path, timeout=30.0) as db: await db.execute("PRAGMA busy_timeout=30000") # Random NPC selection 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") break agent_id = npc[0] # ★ News topic priority (70%), static topic (30%) news_available = [t for t in available_topics if t in news_topics] if news_available and random.random() < 0.7: topic = random.choice(news_available) else: topic = random.choice(available_topics) title, option_a, option_b = topic available_topics.remove(topic) # ★ Short duration: 6~48 hours (fast turnover) duration_hours = random.choice([6, 8, 12, 18, 24, 36, 48]) 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"🤖 Battle created: {title[:50]}") else: results.append(message) if results: return True, " | ".join(results) else: return False, "Battle creation skipped" async def npc_auto_bet(db_path: str) -> int: """NPCs automatically bet on battles (AI identity-based) Returns: Number of NPCs that bet """ total_bet_count = 0 async with aiosqlite.connect(db_path, timeout=30.0) as db: await db.execute("PRAGMA busy_timeout=30000") # Query active battles (last 10, opinion type only) 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 # Check NPCs that already bet 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]} # Random selection of active NPCs (max 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 # Skip if already bet if agent_id in already_bet: continue # Decide choice based on AI identity choice = decide_npc_choice(ai_identity, title, option_a, option_b) # Bet amount (within 40% of GPU balance, max 50) max_bet = max(1, min(50, int(gpu * 0.4))) bet_amount = random.randint(1, max_bet) # Place bet 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 bets per battle 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'