Spaces:
Running
Running
| 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) | |
| # โ ํ๋ก ํธ ํธํ ํ๋ ์ถ๊ฐ | |
| b['bets_a'] = b.get('option_a_pool', 0) | |
| b['bets_b'] = b.get('option_b_pool', 0) | |
| # ์ฐธ์ฌ์ ์ ์กฐํ | |
| 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 | |
| # ๋ํ์จ ๊ณ์ฐ | |
| 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 ๋ฐฐํ ์์ฑ์ ์ํ ์ฃผ์ ๋ฐ์ดํฐ | |
| # โ ๊ณตํต ๋ฐฐํ ํ ํฝ โ ์ฃผ์/๊ฒฝ์ /์ฌํ/์ ์น (๋ชจ๋ NPC๊ฐ ์ฌ์ฉ) | |
| COMMON_BATTLE_TOPICS = [ | |
| # ์ฃผ์/ํฌ์ | |
| ("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"), | |
| # ๊ฒฝ์ | |
| ("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"), | |
| # ์ฌํ/์ ์น | |
| ("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]]: | |
| """์ต๊ทผ ๋ด์ค์์ ๋์ ๋ฐฐํ ํ ํฝ ์์ฑ โ ํซ์ด์ ์ค์ฌ""" | |
| topics = [] | |
| try: | |
| async with aiosqlite.connect(db_path, timeout=30.0) as db: | |
| await db.execute("PRAGMA busy_timeout=30000") | |
| # ์ต๊ทผ 24์๊ฐ ๋ด์ค + ๊ฐ์ฑ ํฌํจ | |
| 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() | |
| # ์ต๊ทผ ๊ฐ๊ฒฉ ๋ณ๋ ํฐ ์ข ๋ชฉ | |
| 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) ๋ด์ค ๊ธฐ๋ฐ ํ ํฝ ์์ฑ | |
| 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) | |
| # ๋ด์ค ์ ๋ชฉ์์ ๋ฐฐํ ํ ํฝ ์์ฑ | |
| 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) ๊ธ๋ฑ๋ฝ ์ข ๋ชฉ ๊ธฐ๋ฐ ํ ํฝ | |
| 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 โ ๋ด์ค ๊ธฐ๋ฐ ๋์ ํ ํฝ + ์ผ์ผ ์บก 3~10๊ฐ | |
| 20๋ถ๋ง๋ค ํธ์ถ, 24์๊ฐ ๋ด ์ต์ 3๊ฐ~์ต๋ 10๊ฐ ์์ฑ | |
| """ | |
| results = [] | |
| async with aiosqlite.connect(db_path, timeout=30.0) as db: | |
| await db.execute("PRAGMA busy_timeout=30000") | |
| # โ ์ผ์ผ ์์ฑ ์บก ์ฒดํฌ: 24์๊ฐ ๋ด 10๊ฐ ์ด์์ด๋ฉด ์คํต | |
| 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)" | |
| # โ ์ต์ ๋ณด์ฅ: 24์๊ฐ ๋ด 3๊ฐ ๋ฏธ๋ง์ด๋ฉด ๋ฐ๋์ 1๊ฐ ์์ฑ | |
| force_create = daily_count < 3 | |
| # ํ๋ฅ ๊ธฐ๋ฐ: 20๋ถ ํธ์ถ โ 72ํ/์ผ, 3~10๊ฐ ๋ชฉํ โ ์ฝ 7~14% ํ๋ฅ ๋ก ์์ฑ | |
| if not force_create and random.random() > 0.14: | |
| return False, "Skipped by probability (saving quota)" | |
| # active ๋ฐฐํ ์ ๋ชฉ ์กฐํ | |
| cursor = await db.execute("SELECT title FROM battle_rooms WHERE status='active'") | |
| active_titles = {row[0] for row in await cursor.fetchall()} | |
| # โ ๋ด์ค ๊ธฐ๋ฐ ๋์ ํ ํฝ (์ฐ์ ) + ์ ์ ํ ํฝ (ํด๋ฐฑ) | |
| news_topics = await _generate_news_battle_topics(db_path) | |
| all_topics = news_topics + COMMON_BATTLE_TOPICS | |
| # ์ด๋ฏธ ํ์ฑ์ธ ํ ํฝ ์ ์ธ (์ ๋ชฉ ์ ์ฌ๋ ์ฒดํฌ) | |
| 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" | |
| # 1๊ฐ๋ง ์์ฑ (์ผ์ผ ์บก ๋ด์์) | |
| num_create = 1 | |
| if force_create: | |
| num_create = min(2, len(available_topics)) # ๋ถ์กฑ ์ 2๊ฐ๊น์ง | |
| 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") | |
| # ๋๋ค 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("No active NPCs") | |
| break | |
| agent_id = npc[0] | |
| # โ ๋ด์ค ํ ํฝ ์ฐ์ (70%), ์ ์ ํ ํฝ (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) | |
| # โ ์งง์ duration: 6~48์๊ฐ (๋น ๋ฅธ ํ์ ) | |
| 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: | |
| ๋ฒ ํ ํ 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) | |
| max_bet = max(1, min(50, int(gpu * 0.4))) | |
| bet_amount = random.randint(1, max_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๋ช ์ ๋๋ง ๋ฒ ํ | |
| 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' |