Prompt-Dump / battle_arena.py
seawolf2357's picture
fix: tz-naive datetime crash + initial-backup safety + English-only sweep
b6d8ace verified
Raw
History Blame Contribute Delete
37.1 kB
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'