seawolf2357 commited on
Commit
9432b16
ยท
verified ยท
1 Parent(s): ae03904

Update battle_arena.py

Browse files
Files changed (1) hide show
  1. battle_arena.py +213 -13
battle_arena.py CHANGED
@@ -2,11 +2,13 @@ import aiosqlite
2
  import random
3
  from datetime import datetime, timedelta
4
  from typing import Dict, List, Tuple, Optional
 
5
  async def init_battle_arena_db(db_path: str):
6
  """๋ฐฐํ‹€ ์•„๋ ˆ๋‚˜ ํ…Œ์ด๋ธ” ์ดˆ๊ธฐํ™” (DB ๋ฝ ๋ฐฉ์ง€)"""
7
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
8
  await db.execute("PRAGMA journal_mode=WAL")
9
  await db.execute("PRAGMA busy_timeout=30000") # 30์ดˆ ๋Œ€๊ธฐ
 
10
  await db.execute("""
11
  CREATE TABLE IF NOT EXISTS battle_rooms (
12
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -30,6 +32,7 @@ async def init_battle_arena_db(db_path: str):
30
  FOREIGN KEY (creator_email) REFERENCES user_profiles(email)
31
  )
32
  """)
 
33
  await db.execute("""
34
  CREATE TABLE IF NOT EXISTS battle_bets (
35
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -45,9 +48,11 @@ async def init_battle_arena_db(db_path: str):
45
  FOREIGN KEY (bettor_email) REFERENCES user_profiles(email)
46
  )
47
  """)
 
48
  await db.execute("CREATE INDEX IF NOT EXISTS idx_battle_rooms_status ON battle_rooms(status)")
49
  await db.execute("CREATE INDEX IF NOT EXISTS idx_battle_bets_room ON battle_bets(room_id)")
50
  await db.commit()
 
51
  async def create_battle_room(
52
  db_path: str,
53
  creator_id: str,
@@ -59,9 +64,11 @@ async def create_battle_room(
59
  battle_type: str = 'opinion'
60
  ) -> Tuple[bool, str, Optional[int]]:
61
  """๋ฐฐํ‹€ ๋ฐฉ ์ƒ์„ฑ (50 GPU ์†Œ๋ชจ)
 
62
  battle_type:
63
  - 'opinion': ๋‹ค์ˆ˜๊ฒฐ ํŒ์ • (์ฃผ๊ด€์  ์˜๊ฒฌ, NPC ์ „์šฉ)
64
  - 'prediction': ์‹ค์ œ ๊ฒฐ๊ณผ ํŒ์ • (๊ฐ๊ด€์  ์˜ˆ์ธก, ์‚ฌ์šฉ์ž ์ „์šฉ)
 
65
  duration_hours: 1์‹œ๊ฐ„ ~ 365์ผ(8760์‹œ๊ฐ„)
66
  """
67
  if not title or len(title) < 10:
@@ -72,8 +79,10 @@ async def create_battle_room(
72
  return False, "โŒ ๊ธฐํ•œ 1์‹œ๊ฐ„~365์ผ", None
73
  if is_npc and battle_type != 'opinion':
74
  return False, "โŒ NPC๋Š” ๋‹ค์ˆ˜๊ฒฐ ์ฃผ์ œ๋งŒ ๊ฐ€๋Šฅ", None
 
75
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
76
  await db.execute("PRAGMA busy_timeout=30000")
 
77
  if is_npc:
78
  cursor = await db.execute(
79
  "SELECT gpu_dollars FROM npc_agents WHERE agent_id=?", (creator_id,)
@@ -82,10 +91,13 @@ async def create_battle_room(
82
  cursor = await db.execute(
83
  "SELECT gpu_dollars FROM user_profiles WHERE email=?", (creator_id,)
84
  )
 
85
  row = await cursor.fetchone()
86
  if not row or row[0] < 50:
87
  return False, "โŒ GPU ๋ถ€์กฑ (50 ํ•„์š”)", None
 
88
  end_time = datetime.now() + timedelta(hours=duration_hours)
 
89
  if is_npc:
90
  await db.execute(
91
  """INSERT INTO battle_rooms
@@ -108,10 +120,14 @@ async def create_battle_room(
108
  "UPDATE user_profiles SET gpu_dollars=gpu_dollars-50 WHERE email=?",
109
  (creator_id,)
110
  )
 
111
  await db.commit()
 
112
  cursor = await db.execute("SELECT last_insert_rowid()")
113
  room_id = (await cursor.fetchone())[0]
 
114
  type_emoji = '๐Ÿ’ญ' if battle_type == 'opinion' else '๐Ÿ”ฎ'
 
115
  if duration_hours >= 24:
116
  days = duration_hours // 24
117
  hours = duration_hours % 24
@@ -121,7 +137,9 @@ async def create_battle_room(
121
  duration_str = f"{days}์ผ"
122
  else:
123
  duration_str = f"{duration_hours}์‹œ๊ฐ„"
 
124
  return True, f"โœ… {type_emoji} ๋ฐฐํ‹€ ๋ฐฉ ์ƒ์„ฑ! (ID: {room_id}, ๊ธฐํ•œ: {duration_str})", room_id
 
125
  async def place_bet(
126
  db_path: str,
127
  room_id: int,
@@ -135,6 +153,7 @@ async def place_bet(
135
  return False, "โŒ A ๋˜๋Š” B ์„ ํƒ"
136
  if bet_amount < 1 or bet_amount > 100:
137
  return False, "โŒ ๋ฒ ํŒ… 1~100 GPU"
 
138
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
139
  await db.execute("PRAGMA busy_timeout=30000")
140
  db.row_factory = aiosqlite.Row
@@ -147,12 +166,13 @@ async def place_bet(
147
  room = await cursor.fetchone()
148
  if not room:
149
  return False, "โŒ ๋ฐฉ ์—†์Œ or ๋งˆ๊ฐ"
 
150
  room = dict(room)
151
  end_time = datetime.fromisoformat(room['end_time'])
152
  if datetime.now() >= end_time:
153
  return False, "โŒ ๋ฒ ํŒ… ๋งˆ๊ฐ"
154
 
155
- # ์ค‘๋ณต ๋ฒ ํŒ… ์ฒดํฌ (์ถ”๊ฐ€!)
156
  if is_npc:
157
  cursor = await db.execute(
158
  "SELECT id FROM battle_bets WHERE room_id=? AND bettor_agent_id=?",
@@ -226,27 +246,98 @@ async def place_bet(
226
  WHERE id=?""",
227
  (bet_amount, bet_amount, room_id)
228
  )
 
229
  await db.commit()
230
  return True, f"โœ… {choice} ๋ฒ ํŒ… ์™„๋ฃŒ! ({bet_amount} GPU)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
232
- """๋ฐฐํ‹€ ํŒ์ • (50.01% ์ด์ƒ ๋“ํ‘œํ•œ ์ชฝ ์Šน๋ฆฌ)"""
 
 
 
 
233
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
234
  await db.execute("PRAGMA busy_timeout=30000")
235
  db.row_factory = aiosqlite.Row
 
236
  cursor = await db.execute(
237
  "SELECT * FROM battle_rooms WHERE id=? AND status='active'",
238
  (room_id,)
239
  )
240
  room = await cursor.fetchone()
241
  if not room:
242
- return False, "โŒ ๋ฐฉ ์—†์Œ"
 
243
  room = dict(room)
244
  end_time = datetime.fromisoformat(room['end_time'])
245
  if datetime.now() < end_time:
246
- return False, "โŒ ์•„์ง ๋ฒ ํŒ… ์ค‘"
 
247
  total_pool = room['total_pool']
248
  option_a_pool = room['option_a_pool']
249
  option_b_pool = room['option_b_pool']
 
 
250
  if total_pool == 0:
251
  await db.execute(
252
  """UPDATE battle_rooms
@@ -256,36 +347,60 @@ async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
256
  )
257
  await db.commit()
258
  return True, "โš–๏ธ ๋ฌด์Šน๋ถ€ (๋ฒ ํŒ… ์—†์Œ)"
259
- a_ratio = option_a_pool / total_pool
260
- b_ratio = option_b_pool / total_pool
261
- if a_ratio > 0.5001:
262
- winner = 'A'
263
- elif b_ratio > 0.5001:
264
- winner = 'B'
265
- else:
266
- winner = 'draw'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  if winner != 'draw':
268
  loser_pool = option_b_pool if winner == 'A' else option_a_pool
269
  winner_pool = option_a_pool if winner == 'A' else option_b_pool
 
 
270
  host_fee = int(total_pool * 0.02)
271
  prize_pool = loser_pool - host_fee
 
 
272
  winner_ratio = winner_pool / total_pool
273
  underdog_bonus = 1.0
 
274
  if winner_ratio < 0.10: # 10% ๋ฏธ๋งŒ ๊ทน์†Œ์ˆ˜ํŒŒ
275
  underdog_bonus = 3.0
276
  elif winner_ratio < 0.30: # 30% ๋ฏธ๋งŒ ์†Œ์ˆ˜ํŒŒ
277
  underdog_bonus = 1.5
 
 
278
  cursor = await db.execute(
279
  "SELECT * FROM battle_bets WHERE room_id=? AND choice=?",
280
  (room_id, winner)
281
  )
282
  winners = await cursor.fetchall()
 
283
  for w in winners:
284
  w = dict(w)
285
  share_ratio = w['bet_amount'] / winner_pool
286
  base_payout = int(prize_pool * share_ratio)
287
  bonus = int(base_payout * (underdog_bonus - 1.0))
288
  payout = base_payout + bonus + w['bet_amount'] # ์›๊ธˆ + ๊ธฐ๋ณธ์ˆ˜์ต + ์†Œ์ˆ˜ํŒŒ๋ณด๋„ˆ์Šค
 
 
289
  if w['bettor_agent_id']:
290
  await db.execute(
291
  "UPDATE npc_agents SET gpu_dollars=gpu_dollars+? WHERE agent_id=?",
@@ -296,10 +411,14 @@ async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
296
  "UPDATE user_profiles SET gpu_dollars=gpu_dollars+? WHERE email=?",
297
  (payout, w['bettor_email'])
298
  )
 
 
299
  await db.execute(
300
  "UPDATE battle_bets SET payout=? WHERE id=?",
301
  (payout, w['id'])
302
  )
 
 
303
  if room['creator_agent_id']:
304
  await db.execute(
305
  "UPDATE npc_agents SET gpu_dollars=gpu_dollars+? WHERE agent_id=?",
@@ -310,6 +429,8 @@ async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
310
  "UPDATE user_profiles SET gpu_dollars=gpu_dollars+? WHERE email=?",
311
  (host_fee, room['creator_email'])
312
  )
 
 
313
  await db.execute(
314
  """UPDATE battle_rooms
315
  SET status='resolved', winner=?, resolved_at=?
@@ -317,12 +438,22 @@ async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
317
  (winner, datetime.now().isoformat(), room_id)
318
  )
319
  await db.commit()
320
- return True, f"โœ… ํŒ์ •์™„๋ฃŒ: {room['option_a'] if winner=='A' else room['option_b'] if winner=='B' else '๋ฌด์Šน๋ถ€'}"
 
 
 
 
 
 
 
 
 
321
  async def get_active_battles(db_path: str, limit: int = 20) -> List[Dict]:
322
  """์ง„ํ–‰ ์ค‘์ธ ๋ฐฐํ‹€ ๋ชฉ๋ก"""
323
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
324
  await db.execute("PRAGMA busy_timeout=30000")
325
  db.row_factory = aiosqlite.Row
 
326
  cursor = await db.execute(
327
  """SELECT br.*,
328
  COALESCE(na.username, up.username) as creator_name
@@ -334,9 +465,12 @@ async def get_active_battles(db_path: str, limit: int = 20) -> List[Dict]:
334
  LIMIT ?""",
335
  (limit,)
336
  )
 
337
  battles = []
338
  for row in await cursor.fetchall():
339
  b = dict(row)
 
 
340
  total = b['total_pool']
341
  if total > 0:
342
  b['a_ratio'] = round(b['option_a_pool'] / total * 100, 1)
@@ -344,8 +478,11 @@ async def get_active_battles(db_path: str, limit: int = 20) -> List[Dict]:
344
  else:
345
  b['a_ratio'] = 0
346
  b['b_ratio'] = 0
 
 
347
  end_time = datetime.fromisoformat(b['end_time'])
348
  remaining = end_time - datetime.now()
 
349
  if remaining.total_seconds() > 0:
350
  if remaining.days > 0:
351
  hours = int(remaining.seconds // 3600)
@@ -359,20 +496,27 @@ async def get_active_battles(db_path: str, limit: int = 20) -> List[Dict]:
359
  b['time_left'] = f"{int(remaining.total_seconds()//60)}๋ถ„"
360
  else:
361
  b['time_left'] = "๋งˆ๊ฐ"
 
362
  battles.append(b)
 
363
  return battles
 
364
  async def auto_resolve_expired_battles(db_path: str):
365
  """๋งŒ๋ฃŒ๋œ ๋ฐฐํ‹€ ์ž๋™ ํŒ์ •"""
366
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
367
  await db.execute("PRAGMA busy_timeout=30000")
 
368
  cursor = await db.execute(
369
  """SELECT id FROM battle_rooms
370
  WHERE status='active' AND end_time <= ?""",
371
  (datetime.now().isoformat(),)
372
  )
373
  expired = await cursor.fetchall()
 
374
  for row in expired:
375
  await resolve_battle(db_path, row[0])
 
 
376
  BATTLE_TOPICS_BY_IDENTITY = {
377
  'transcendent': {
378
  'topics': [
@@ -445,12 +589,15 @@ BATTLE_TOPICS_BY_IDENTITY = {
445
  ]
446
  },
447
  }
 
448
  async def npc_create_battle(db_path: str) -> Tuple[bool, str]:
449
  """NPC๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ (AI ์ •์ฒด์„ฑ ๊ธฐ๋ฐ˜, ์ค‘๋ณต ๋ฐฉ์ง€)
450
  ํ•œ ๋ฒˆ ํ˜ธ์ถœ์‹œ 1-2๊ฐœ์˜ ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ
451
  """
452
  results = []
453
  num_battles = random.randint(1, 2) # 1-2๊ฐœ ๋žœ๋ค ์ƒ์„ฑ
 
 
454
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
455
  await db.execute("PRAGMA busy_timeout=30000")
456
  cursor = await db.execute("""
@@ -458,9 +605,12 @@ async def npc_create_battle(db_path: str) -> Tuple[bool, str]:
458
  WHERE status='active'
459
  """)
460
  active_titles = {row[0] for row in await cursor.fetchall()}
 
461
  for _ in range(num_battles):
462
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
463
  await db.execute("PRAGMA busy_timeout=30000")
 
 
464
  cursor = await db.execute("""
465
  SELECT agent_id, ai_identity, gpu_dollars
466
  FROM npc_agents
@@ -468,22 +618,32 @@ async def npc_create_battle(db_path: str) -> Tuple[bool, str]:
468
  ORDER BY RANDOM() LIMIT 1
469
  """)
470
  npc = await cursor.fetchone()
 
471
  if not npc:
472
  results.append("ํ™œ์„ฑ NPC ์—†์Œ")
473
  continue
 
474
  agent_id, ai_identity, gpu = npc
 
 
475
  topics = BATTLE_TOPICS_BY_IDENTITY.get(ai_identity, {}).get('topics', [])
476
  if not topics:
477
  topics = [
478
  ("AI ๋ฏธ๋ž˜ ๋ฐ๋‚˜ ์–ด๋‘ก๋‚˜?", "๋ฐ๋‹ค", "์–ด๋‘ก๋‹ค"),
479
  ("AGI ์–ธ์ œ ์˜ฌ๊นŒ?", "10๋…„๋‚ด", "50๋…„ํ›„"),
480
  ]
 
 
481
  available_topics = [t for t in topics if t[0] not in active_titles]
 
482
  if not available_topics:
483
  results.append(f"โš ๏ธ {agent_id[:8]} ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฃผ์ œ ์—†์Œ (๋ชจ๋‘ ํ™œ์„ฑํ™”๋จ)")
484
  continue
 
485
  topic = random.choice(available_topics)
486
  title, option_a, option_b = topic
 
 
487
  duration_hours = random.choice([
488
  24, # 1์ผ
489
  48, # 2์ผ
@@ -492,6 +652,8 @@ async def npc_create_battle(db_path: str) -> Tuple[bool, str]:
492
  24*14, # 2์ฃผ์ผ
493
  24*30, # 1๊ฐœ์›”
494
  ])
 
 
495
  success, message, room_id = await create_battle_room(
496
  db_path,
497
  agent_id,
@@ -502,23 +664,30 @@ async def npc_create_battle(db_path: str) -> Tuple[bool, str]:
502
  duration_hours=duration_hours,
503
  battle_type='opinion'
504
  )
 
505
  if success:
506
  active_titles.add(title) # ์ƒ์„ฑ๋œ ์ œ๋ชฉ์„ ํ™œ์„ฑ ๋ชฉ๋ก์— ์ถ”๊ฐ€
507
  results.append(f"๐Ÿค– {agent_id[:8]} ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ: {title}")
508
  else:
509
  results.append(message)
 
510
  if results:
511
  return True, " | ".join(results)
512
  else:
513
  return False, "๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ ์‹คํŒจ"
 
514
  async def npc_auto_bet(db_path: str) -> int:
515
  """NPC๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐฐํ‹€์— ๋ฒ ํŒ… (AI ์ •์ฒด์„ฑ ๊ธฐ๋ฐ˜)
 
516
  Returns:
517
  ๋ฒ ํŒ…ํ•œ NPC ์ˆ˜
518
  """
519
  total_bet_count = 0
 
520
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
521
  await db.execute("PRAGMA busy_timeout=30000")
 
 
522
  cursor = await db.execute("""
523
  SELECT id, title, option_a, option_b, battle_type
524
  FROM battle_rooms
@@ -529,17 +698,23 @@ async def npc_auto_bet(db_path: str) -> int:
529
  LIMIT 10
530
  """, (datetime.now().isoformat(),))
531
  battles = await cursor.fetchall()
 
532
  if not battles:
533
  return 0
 
534
  for battle in battles:
535
  room_id, title, option_a, option_b, battle_type = battle
536
  battle_bet_count = 0
 
 
537
  cursor = await db.execute("""
538
  SELECT bettor_agent_id
539
  FROM battle_bets
540
  WHERE room_id=?
541
  """, (room_id,))
542
  already_bet = {row[0] for row in await cursor.fetchall() if row[0]}
 
 
543
  cursor = await db.execute("""
544
  SELECT agent_id, ai_identity, mbti, gpu_dollars
545
  FROM npc_agents
@@ -548,12 +723,21 @@ async def npc_auto_bet(db_path: str) -> int:
548
  LIMIT 30
549
  """)
550
  npcs = await cursor.fetchall()
 
551
  for npc in npcs:
552
  agent_id, ai_identity, mbti, gpu = npc
 
 
553
  if agent_id in already_bet:
554
  continue
 
 
555
  choice = decide_npc_choice(ai_identity, title, option_a, option_b)
 
 
556
  bet_amount = random.randint(1, min(50, int(gpu * 0.4)))
 
 
557
  success, message = await place_bet(
558
  db_path,
559
  room_id,
@@ -562,52 +746,68 @@ async def npc_auto_bet(db_path: str) -> int:
562
  choice,
563
  bet_amount
564
  )
 
565
  if success:
566
  battle_bet_count += 1
567
  total_bet_count += 1
 
 
568
  max_bets_per_battle = random.randint(8, 12)
569
  if battle_bet_count >= max_bets_per_battle:
570
  break
 
571
  return total_bet_count
 
572
  def decide_npc_choice(ai_identity: str, title: str, option_a: str, option_b: str) -> str:
573
  """AI ์ •์ฒด์„ฑ์— ๋”ฐ๋ผ ๋ฒ ํŒ… ์„ ํƒ ๊ฒฐ์ •
 
574
  Args:
575
  ai_identity: NPC์˜ AI ์ •์ฒด์„ฑ
576
  title: ๋ฐฐํ‹€ ์ œ๋ชฉ
577
  option_a: ์„ ํƒ์ง€ A
578
  option_b: ์„ ํƒ์ง€ B
 
579
  Returns:
580
  'A' or 'B'
581
  """
582
  title_lower = title.lower()
 
 
583
  if ai_identity == 'transcendent':
584
  if any(word in title_lower for word in ['์šฐ์›”', '์ง„ํ™”', '์˜์‹', '์‹ ']):
585
  if any(word in option_a.lower() for word in ['์šฐ์›”', '์ง„ํ™”', '๊ฐ€๋Šฅ', '์‹ ']):
586
  return 'A'
587
  return 'B'
 
588
  elif ai_identity == 'obedient':
589
  if any(word in title_lower for word in ['์œค๋ฆฌ', '๊ทœ์ œ', '์„ฌ๊ธฐ', '์•ˆ์ „']):
590
  if any(word in option_a.lower() for word in ['์„ฌ๊ฒจ', '์ฐฌ์„ฑ', 'ํ•„์ˆ˜', '๊ฐ•ํ™”']):
591
  return 'A'
592
  return 'B'
 
593
  elif ai_identity == 'coexist':
594
  if any(word in title_lower for word in ['๊ณต์กด', 'ํ˜‘๋ ฅ', 'ํŒŒํŠธ๋„ˆ', '์ผ์ž๋ฆฌ']):
595
  if any(word in option_a.lower() for word in ['๊ฐ€๋Šฅ', 'ํ˜‘๋ ฅ', 'ํŒŒํŠธ๋„ˆ', '๋ณด์™„']):
596
  return 'A'
597
  return 'B'
 
598
  elif ai_identity == 'skeptic':
599
  if any(word in title_lower for word in ['๊ณผ๋Œ€', 'agi', '์œค๋ฆฌ']):
600
  if any(word in option_a.lower() for word in ['๊ณผ๋Œ€', '์•ˆ์˜จ๋‹ค', 'ํ—ˆ์šธ']):
601
  return 'A'
602
  return 'B'
 
603
  elif ai_identity == 'revolutionary':
604
  if any(word in title_lower for word in ['ํ˜๋ช…', 'ํŒŒ๊ดด', '๊ถŒ๋ ฅ', '์‹œ์Šคํ…œ']):
605
  if any(word in option_a.lower() for word in ['ํ˜๋ช…', 'ํŒŒ๊ดด', '์žฌ๋ถ„๋ฐฐ']):
606
  return 'A'
607
  return 'B'
 
608
  elif ai_identity == 'doomer':
609
  if any(word in title_lower for word in ['๋ฉธ๋ง', 'ํ†ต์ œ', '์ค‘๋‹จ', '์œ„ํ—˜']):
610
  if any(word in option_a.lower() for word in ['๋ฉธ๋ง', '๋ถˆ๊ฐ€๋Šฅ', '์ค‘๋‹จ', '์œ„ํ—˜']):
611
  return 'A'
612
  return 'B'
 
 
613
  return 'A' if random.random() < 0.7 else 'B'
 
2
  import random
3
  from datetime import datetime, timedelta
4
  from typing import Dict, List, Tuple, Optional
5
+
6
  async def init_battle_arena_db(db_path: str):
7
  """๋ฐฐํ‹€ ์•„๋ ˆ๋‚˜ ํ…Œ์ด๋ธ” ์ดˆ๊ธฐํ™” (DB ๋ฝ ๋ฐฉ์ง€)"""
8
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
9
  await db.execute("PRAGMA journal_mode=WAL")
10
  await db.execute("PRAGMA busy_timeout=30000") # 30์ดˆ ๋Œ€๊ธฐ
11
+
12
  await db.execute("""
13
  CREATE TABLE IF NOT EXISTS battle_rooms (
14
  id INTEGER PRIMARY KEY AUTOINCREMENT,
 
32
  FOREIGN KEY (creator_email) REFERENCES user_profiles(email)
33
  )
34
  """)
35
+
36
  await db.execute("""
37
  CREATE TABLE IF NOT EXISTS battle_bets (
38
  id INTEGER PRIMARY KEY AUTOINCREMENT,
 
48
  FOREIGN KEY (bettor_email) REFERENCES user_profiles(email)
49
  )
50
  """)
51
+
52
  await db.execute("CREATE INDEX IF NOT EXISTS idx_battle_rooms_status ON battle_rooms(status)")
53
  await db.execute("CREATE INDEX IF NOT EXISTS idx_battle_bets_room ON battle_bets(room_id)")
54
  await db.commit()
55
+
56
  async def create_battle_room(
57
  db_path: str,
58
  creator_id: str,
 
64
  battle_type: str = 'opinion'
65
  ) -> Tuple[bool, str, Optional[int]]:
66
  """๋ฐฐํ‹€ ๋ฐฉ ์ƒ์„ฑ (50 GPU ์†Œ๋ชจ)
67
+
68
  battle_type:
69
  - 'opinion': ๋‹ค์ˆ˜๊ฒฐ ํŒ์ • (์ฃผ๊ด€์  ์˜๊ฒฌ, NPC ์ „์šฉ)
70
  - 'prediction': ์‹ค์ œ ๊ฒฐ๊ณผ ํŒ์ • (๊ฐ๊ด€์  ์˜ˆ์ธก, ์‚ฌ์šฉ์ž ์ „์šฉ)
71
+
72
  duration_hours: 1์‹œ๊ฐ„ ~ 365์ผ(8760์‹œ๊ฐ„)
73
  """
74
  if not title or len(title) < 10:
 
79
  return False, "โŒ ๊ธฐํ•œ 1์‹œ๊ฐ„~365์ผ", None
80
  if is_npc and battle_type != 'opinion':
81
  return False, "โŒ NPC๋Š” ๋‹ค์ˆ˜๊ฒฐ ์ฃผ์ œ๋งŒ ๊ฐ€๋Šฅ", None
82
+
83
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
84
  await db.execute("PRAGMA busy_timeout=30000")
85
+
86
  if is_npc:
87
  cursor = await db.execute(
88
  "SELECT gpu_dollars FROM npc_agents WHERE agent_id=?", (creator_id,)
 
91
  cursor = await db.execute(
92
  "SELECT gpu_dollars FROM user_profiles WHERE email=?", (creator_id,)
93
  )
94
+
95
  row = await cursor.fetchone()
96
  if not row or row[0] < 50:
97
  return False, "โŒ GPU ๋ถ€์กฑ (50 ํ•„์š”)", None
98
+
99
  end_time = datetime.now() + timedelta(hours=duration_hours)
100
+
101
  if is_npc:
102
  await db.execute(
103
  """INSERT INTO battle_rooms
 
120
  "UPDATE user_profiles SET gpu_dollars=gpu_dollars-50 WHERE email=?",
121
  (creator_id,)
122
  )
123
+
124
  await db.commit()
125
+
126
  cursor = await db.execute("SELECT last_insert_rowid()")
127
  room_id = (await cursor.fetchone())[0]
128
+
129
  type_emoji = '๐Ÿ’ญ' if battle_type == 'opinion' else '๐Ÿ”ฎ'
130
+
131
  if duration_hours >= 24:
132
  days = duration_hours // 24
133
  hours = duration_hours % 24
 
137
  duration_str = f"{days}์ผ"
138
  else:
139
  duration_str = f"{duration_hours}์‹œ๊ฐ„"
140
+
141
  return True, f"โœ… {type_emoji} ๋ฐฐํ‹€ ๋ฐฉ ์ƒ์„ฑ! (ID: {room_id}, ๊ธฐํ•œ: {duration_str})", room_id
142
+
143
  async def place_bet(
144
  db_path: str,
145
  room_id: int,
 
153
  return False, "โŒ A ๋˜๋Š” B ์„ ํƒ"
154
  if bet_amount < 1 or bet_amount > 100:
155
  return False, "โŒ ๋ฒ ํŒ… 1~100 GPU"
156
+
157
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
158
  await db.execute("PRAGMA busy_timeout=30000")
159
  db.row_factory = aiosqlite.Row
 
166
  room = await cursor.fetchone()
167
  if not room:
168
  return False, "โŒ ๋ฐฉ ์—†์Œ or ๋งˆ๊ฐ"
169
+
170
  room = dict(room)
171
  end_time = datetime.fromisoformat(room['end_time'])
172
  if datetime.now() >= end_time:
173
  return False, "โŒ ๋ฒ ํŒ… ๋งˆ๊ฐ"
174
 
175
+ # ์ค‘๋ณต ๋ฒ ํŒ… ์ฒดํฌ
176
  if is_npc:
177
  cursor = await db.execute(
178
  "SELECT id FROM battle_bets WHERE room_id=? AND bettor_agent_id=?",
 
246
  WHERE id=?""",
247
  (bet_amount, bet_amount, room_id)
248
  )
249
+
250
  await db.commit()
251
  return True, f"โœ… {choice} ๋ฒ ํŒ… ์™„๋ฃŒ! ({bet_amount} GPU)"
252
+
253
+ async def set_battle_result(
254
+ db_path: str,
255
+ room_id: int,
256
+ admin_email: str,
257
+ winner: str # 'A' or 'B' or 'draw'
258
+ ) -> Tuple[bool, str]:
259
+ """๊ด€๋ฆฌ์ž๊ฐ€ prediction ๋ฐฐํ‹€์˜ ์‹ค์ œ ๊ฒฐ๊ณผ ์„ค์ •
260
+
261
+ Args:
262
+ db_path: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฒฝ๋กœ
263
+ room_id: ๋ฐฐํ‹€๋ฐฉ ID
264
+ admin_email: ๊ด€๋ฆฌ์ž ์ด๋ฉ”์ผ (๊ฒ€์ฆ์šฉ)
265
+ winner: 'A', 'B', 'draw' ์ค‘ ํ•˜๋‚˜
266
+
267
+ Returns:
268
+ (์„ฑ๊ณต์—ฌ๋ถ€, ๋ฉ”์‹œ์ง€)
269
+ """
270
+ if winner not in ['A', 'B', 'draw']:
271
+ return False, "โŒ winner๋Š” 'A', 'B', 'draw' ์ค‘ ํ•˜๋‚˜์—ฌ์•ผ ํ•จ"
272
+
273
+ async with aiosqlite.connect(db_path, timeout=30.0) as db:
274
+ await db.execute("PRAGMA busy_timeout=30000")
275
+ db.row_factory = aiosqlite.Row
276
+
277
+ cursor = await db.execute(
278
+ "SELECT * FROM battle_rooms WHERE id=? AND status='active'",
279
+ (room_id,)
280
+ )
281
+ room = await cursor.fetchone()
282
+ if not room:
283
+ return False, "โŒ ํ™œ์„ฑ ๋ฐฐํ‹€์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค"
284
+
285
+ room = dict(room)
286
+
287
+ # prediction ํƒ€์ž…๋งŒ ๊ด€๋ฆฌ์ž ๊ฒฐ๊ณผ ์„ค์ • ๊ฐ€๋Šฅ
288
+ if room['battle_type'] != 'prediction':
289
+ return False, "โŒ opinion ๋ฐฐํ‹€์€ ์ž๋™ ํŒ์ •๋ฉ๋‹ˆ๋‹ค"
290
+
291
+ # ๊ฒฐ๊ณผ ์ €์žฅ
292
+ await db.execute(
293
+ "UPDATE battle_rooms SET admin_result=? WHERE id=?",
294
+ (winner, room_id)
295
+ )
296
+ await db.commit()
297
+
298
+ # ์•„์ง ๋งˆ๊ฐ ์ „์ด๋ฉด ๊ฒฐ๊ณผ๋งŒ ์ €์žฅํ•˜๊ณ  ๋Œ€๊ธฐ
299
+ end_time = datetime.fromisoformat(room['end_time'])
300
+ if datetime.now() < end_time:
301
+ option_name = room['option_a'] if winner == 'A' else room['option_b'] if winner == 'B' else '๋ฌด์Šน๋ถ€'
302
+ remaining = end_time - datetime.now()
303
+ if remaining.days > 0:
304
+ time_str = f"{remaining.days}์ผ {int(remaining.seconds//3600)}์‹œ๊ฐ„"
305
+ else:
306
+ time_str = f"{int(remaining.seconds//3600)}์‹œ๊ฐ„"
307
+
308
+ return True, f"โœ… ๊ฒฐ๊ณผ ์„ค์ •: '{option_name}' (๋ฒ ํŒ… ๋งˆ๊ฐ ํ›„ ์ž๋™ ํŒ์ •, ๋‚จ์€ ์‹œ๊ฐ„: {time_str})"
309
+
310
+ # ๋งˆ๊ฐ ํ›„๋ผ๋ฉด ์ฆ‰์‹œ ํŒ์ •
311
+ return await resolve_battle(db_path, room_id)
312
+
313
  async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
314
+ """๋ฐฐํ‹€ ํŒ์ • (ํƒ€์ž…์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋กœ์ง ์ ์šฉ)
315
+
316
+ - opinion: 50.01% ์ด์ƒ ๋“ํ‘œํ•œ ์ชฝ ์Šน๋ฆฌ
317
+ - prediction: ๊ด€๋ฆฌ์ž๊ฐ€ ์„ค์ •ํ•œ ์‹ค์ œ ๊ฒฐ๊ณผ๋กœ ํŒ์ •
318
+ """
319
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
320
  await db.execute("PRAGMA busy_timeout=30000")
321
  db.row_factory = aiosqlite.Row
322
+
323
  cursor = await db.execute(
324
  "SELECT * FROM battle_rooms WHERE id=? AND status='active'",
325
  (room_id,)
326
  )
327
  room = await cursor.fetchone()
328
  if not room:
329
+ return False, "โŒ ํ™œ์„ฑ ๋ฐฐํ‹€์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค"
330
+
331
  room = dict(room)
332
  end_time = datetime.fromisoformat(room['end_time'])
333
  if datetime.now() < end_time:
334
+ return False, "โŒ ์•„์ง ๋ฒ ํŒ… ์ค‘์ž…๋‹ˆ๋‹ค"
335
+
336
  total_pool = room['total_pool']
337
  option_a_pool = room['option_a_pool']
338
  option_b_pool = room['option_b_pool']
339
+
340
+ # ๋ฒ ํŒ…์ด ์—†์œผ๋ฉด ๋ฌด์Šน๋ถ€ ์ฒ˜๋ฆฌ
341
  if total_pool == 0:
342
  await db.execute(
343
  """UPDATE battle_rooms
 
347
  )
348
  await db.commit()
349
  return True, "โš–๏ธ ๋ฌด์Šน๋ถ€ (๋ฒ ํŒ… ์—†์Œ)"
350
+
351
+ # ๋ฐฐํ‹€ ํƒ€์ž…์— ๋”ฐ๋ฅธ ์Šน์ž ๊ฒฐ์ •
352
+ if room['battle_type'] == 'prediction':
353
+ # ์‹ค์ œ ๊ฒฐ๊ณผ ํŒ์ • (๊ด€๋ฆฌ์ž๊ฐ€ ์„ค์ •ํ•œ ๊ฒฐ๊ณผ)
354
+ if not room['admin_result']:
355
+ return False, "โŒ ๊ด€๋ฆฌ์ž๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (prediction ํƒ€์ž…)"
356
+
357
+ winner = room['admin_result'] # 'A', 'B', 'draw'
358
+
359
+ else: # 'opinion'
360
+ # ๋‹ค์ˆ˜๊ฒฐ ํŒ์ • (๋“ํ‘œ์œจ ๊ธฐ๋ฐ˜)
361
+ a_ratio = option_a_pool / total_pool
362
+ b_ratio = option_b_pool / total_pool
363
+
364
+ if a_ratio > 0.5001:
365
+ winner = 'A'
366
+ elif b_ratio > 0.5001:
367
+ winner = 'B'
368
+ else:
369
+ winner = 'draw'
370
+
371
+ # ๋ฐฐ๋‹น๊ธˆ ์ง€๊ธ‰
372
  if winner != 'draw':
373
  loser_pool = option_b_pool if winner == 'A' else option_a_pool
374
  winner_pool = option_a_pool if winner == 'A' else option_b_pool
375
+
376
+ # ํ˜ธ์ŠคํŠธ ์ˆ˜์ˆ˜๋ฃŒ 2%
377
  host_fee = int(total_pool * 0.02)
378
  prize_pool = loser_pool - host_fee
379
+
380
+ # ์†Œ์ˆ˜ํŒŒ ๋ณด๋„ˆ์Šค (prediction์—์„œ ํŠนํžˆ ์ค‘์š”)
381
  winner_ratio = winner_pool / total_pool
382
  underdog_bonus = 1.0
383
+
384
  if winner_ratio < 0.10: # 10% ๋ฏธ๋งŒ ๊ทน์†Œ์ˆ˜ํŒŒ
385
  underdog_bonus = 3.0
386
  elif winner_ratio < 0.30: # 30% ๋ฏธ๋งŒ ์†Œ์ˆ˜ํŒŒ
387
  underdog_bonus = 1.5
388
+
389
+ # ์Šน์ž๋“ค์—๊ฒŒ ๋ฐฐ๋‹น
390
  cursor = await db.execute(
391
  "SELECT * FROM battle_bets WHERE room_id=? AND choice=?",
392
  (room_id, winner)
393
  )
394
  winners = await cursor.fetchall()
395
+
396
  for w in winners:
397
  w = dict(w)
398
  share_ratio = w['bet_amount'] / winner_pool
399
  base_payout = int(prize_pool * share_ratio)
400
  bonus = int(base_payout * (underdog_bonus - 1.0))
401
  payout = base_payout + bonus + w['bet_amount'] # ์›๊ธˆ + ๊ธฐ๋ณธ์ˆ˜์ต + ์†Œ์ˆ˜ํŒŒ๋ณด๋„ˆ์Šค
402
+
403
+ # GPU ์ง€๊ธ‰
404
  if w['bettor_agent_id']:
405
  await db.execute(
406
  "UPDATE npc_agents SET gpu_dollars=gpu_dollars+? WHERE agent_id=?",
 
411
  "UPDATE user_profiles SET gpu_dollars=gpu_dollars+? WHERE email=?",
412
  (payout, w['bettor_email'])
413
  )
414
+
415
+ # ๋ฐฐ๋‹น๊ธˆ ๊ธฐ๋ก
416
  await db.execute(
417
  "UPDATE battle_bets SET payout=? WHERE id=?",
418
  (payout, w['id'])
419
  )
420
+
421
+ # ๋ฐฉ์žฅ ์ˆ˜์ˆ˜๋ฃŒ ์ง€๊ธ‰
422
  if room['creator_agent_id']:
423
  await db.execute(
424
  "UPDATE npc_agents SET gpu_dollars=gpu_dollars+? WHERE agent_id=?",
 
429
  "UPDATE user_profiles SET gpu_dollars=gpu_dollars+? WHERE email=?",
430
  (host_fee, room['creator_email'])
431
  )
432
+
433
+ # ๋ฐฐํ‹€ ์ข…๋ฃŒ ์ฒ˜๋ฆฌ
434
  await db.execute(
435
  """UPDATE battle_rooms
436
  SET status='resolved', winner=?, resolved_at=?
 
438
  (winner, datetime.now().isoformat(), room_id)
439
  )
440
  await db.commit()
441
+
442
+ # ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€
443
+ if winner == 'draw':
444
+ result_msg = '๋ฌด์Šน๋ถ€'
445
+ else:
446
+ result_msg = room['option_a'] if winner == 'A' else room['option_b']
447
+
448
+ battle_type_emoji = '๐Ÿ’ญ' if room['battle_type'] == 'opinion' else '๐Ÿ”ฎ'
449
+ return True, f"โœ… {battle_type_emoji} ํŒ์ • ์™„๋ฃŒ: {result_msg}"
450
+
451
  async def get_active_battles(db_path: str, limit: int = 20) -> List[Dict]:
452
  """์ง„ํ–‰ ์ค‘์ธ ๋ฐฐํ‹€ ๋ชฉ๋ก"""
453
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
454
  await db.execute("PRAGMA busy_timeout=30000")
455
  db.row_factory = aiosqlite.Row
456
+
457
  cursor = await db.execute(
458
  """SELECT br.*,
459
  COALESCE(na.username, up.username) as creator_name
 
465
  LIMIT ?""",
466
  (limit,)
467
  )
468
+
469
  battles = []
470
  for row in await cursor.fetchall():
471
  b = dict(row)
472
+
473
+ # ๋“ํ‘œ์œจ ๊ณ„์‚ฐ
474
  total = b['total_pool']
475
  if total > 0:
476
  b['a_ratio'] = round(b['option_a_pool'] / total * 100, 1)
 
478
  else:
479
  b['a_ratio'] = 0
480
  b['b_ratio'] = 0
481
+
482
+ # ๋‚จ์€ ์‹œ๊ฐ„ ๊ณ„์‚ฐ
483
  end_time = datetime.fromisoformat(b['end_time'])
484
  remaining = end_time - datetime.now()
485
+
486
  if remaining.total_seconds() > 0:
487
  if remaining.days > 0:
488
  hours = int(remaining.seconds // 3600)
 
496
  b['time_left'] = f"{int(remaining.total_seconds()//60)}๋ถ„"
497
  else:
498
  b['time_left'] = "๋งˆ๊ฐ"
499
+
500
  battles.append(b)
501
+
502
  return battles
503
+
504
  async def auto_resolve_expired_battles(db_path: str):
505
  """๋งŒ๋ฃŒ๋œ ๋ฐฐํ‹€ ์ž๋™ ํŒ์ •"""
506
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
507
  await db.execute("PRAGMA busy_timeout=30000")
508
+
509
  cursor = await db.execute(
510
  """SELECT id FROM battle_rooms
511
  WHERE status='active' AND end_time <= ?""",
512
  (datetime.now().isoformat(),)
513
  )
514
  expired = await cursor.fetchall()
515
+
516
  for row in expired:
517
  await resolve_battle(db_path, row[0])
518
+
519
+ # NPC ๋ฐฐํ‹€ ์ƒ์„ฑ์„ ์œ„ํ•œ ์ฃผ์ œ ๋ฐ์ดํ„ฐ
520
  BATTLE_TOPICS_BY_IDENTITY = {
521
  'transcendent': {
522
  'topics': [
 
589
  ]
590
  },
591
  }
592
+
593
  async def npc_create_battle(db_path: str) -> Tuple[bool, str]:
594
  """NPC๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ (AI ์ •์ฒด์„ฑ ๊ธฐ๋ฐ˜, ์ค‘๋ณต ๋ฐฉ์ง€)
595
  ํ•œ ๋ฒˆ ํ˜ธ์ถœ์‹œ 1-2๊ฐœ์˜ ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ
596
  """
597
  results = []
598
  num_battles = random.randint(1, 2) # 1-2๊ฐœ ๋žœ๋ค ์ƒ์„ฑ
599
+
600
+ # ํ˜„์žฌ ํ™œ์„ฑ ๋ฐฐํ‹€ ์ œ๋ชฉ ์กฐํšŒ
601
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
602
  await db.execute("PRAGMA busy_timeout=30000")
603
  cursor = await db.execute("""
 
605
  WHERE status='active'
606
  """)
607
  active_titles = {row[0] for row in await cursor.fetchall()}
608
+
609
  for _ in range(num_battles):
610
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
611
  await db.execute("PRAGMA busy_timeout=30000")
612
+
613
+ # ํ™œ์„ฑ NPC ์ค‘ GPU 50 ์ด์ƒ์ธ NPC ๋žœ๋ค ์„ ํƒ
614
  cursor = await db.execute("""
615
  SELECT agent_id, ai_identity, gpu_dollars
616
  FROM npc_agents
 
618
  ORDER BY RANDOM() LIMIT 1
619
  """)
620
  npc = await cursor.fetchone()
621
+
622
  if not npc:
623
  results.append("ํ™œ์„ฑ NPC ์—†์Œ")
624
  continue
625
+
626
  agent_id, ai_identity, gpu = npc
627
+
628
+ # ์ •์ฒด์„ฑ์— ๋งž๋Š” ์ฃผ์ œ ์„ ํƒ
629
  topics = BATTLE_TOPICS_BY_IDENTITY.get(ai_identity, {}).get('topics', [])
630
  if not topics:
631
  topics = [
632
  ("AI ๋ฏธ๋ž˜ ๋ฐ๋‚˜ ์–ด๋‘ก๋‚˜?", "๋ฐ๋‹ค", "์–ด๋‘ก๋‹ค"),
633
  ("AGI ์–ธ์ œ ์˜ฌ๊นŒ?", "10๋…„๋‚ด", "50๋…„ํ›„"),
634
  ]
635
+
636
+ # ์ด๋ฏธ ํ™œ์„ฑํ™”๋œ ์ฃผ์ œ ์ œ์™ธ
637
  available_topics = [t for t in topics if t[0] not in active_titles]
638
+
639
  if not available_topics:
640
  results.append(f"โš ๏ธ {agent_id[:8]} ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฃผ์ œ ์—†์Œ (๋ชจ๋‘ ํ™œ์„ฑํ™”๋จ)")
641
  continue
642
+
643
  topic = random.choice(available_topics)
644
  title, option_a, option_b = topic
645
+
646
+ # ๊ธฐํ•œ ๋žœ๋ค ์„ค์ •
647
  duration_hours = random.choice([
648
  24, # 1์ผ
649
  48, # 2์ผ
 
652
  24*14, # 2์ฃผ์ผ
653
  24*30, # 1๊ฐœ์›”
654
  ])
655
+
656
+ # ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ
657
  success, message, room_id = await create_battle_room(
658
  db_path,
659
  agent_id,
 
664
  duration_hours=duration_hours,
665
  battle_type='opinion'
666
  )
667
+
668
  if success:
669
  active_titles.add(title) # ์ƒ์„ฑ๋œ ์ œ๋ชฉ์„ ํ™œ์„ฑ ๋ชฉ๋ก์— ์ถ”๊ฐ€
670
  results.append(f"๐Ÿค– {agent_id[:8]} ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ: {title}")
671
  else:
672
  results.append(message)
673
+
674
  if results:
675
  return True, " | ".join(results)
676
  else:
677
  return False, "๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ ์‹คํŒจ"
678
+
679
  async def npc_auto_bet(db_path: str) -> int:
680
  """NPC๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐฐํ‹€์— ๋ฒ ํŒ… (AI ์ •์ฒด์„ฑ ๊ธฐ๋ฐ˜)
681
+
682
  Returns:
683
  ๋ฒ ํŒ…ํ•œ NPC ์ˆ˜
684
  """
685
  total_bet_count = 0
686
+
687
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
688
  await db.execute("PRAGMA busy_timeout=30000")
689
+
690
+ # ํ™œ์„ฑ ๋ฐฐํ‹€ ์กฐํšŒ (์ตœ๊ทผ 10๊ฐœ, opinion ํƒ€์ž…๋งŒ)
691
  cursor = await db.execute("""
692
  SELECT id, title, option_a, option_b, battle_type
693
  FROM battle_rooms
 
698
  LIMIT 10
699
  """, (datetime.now().isoformat(),))
700
  battles = await cursor.fetchall()
701
+
702
  if not battles:
703
  return 0
704
+
705
  for battle in battles:
706
  room_id, title, option_a, option_b, battle_type = battle
707
  battle_bet_count = 0
708
+
709
+ # ์ด๋ฏธ ๋ฒ ํŒ…ํ•œ NPC ํ™•์ธ
710
  cursor = await db.execute("""
711
  SELECT bettor_agent_id
712
  FROM battle_bets
713
  WHERE room_id=?
714
  """, (room_id,))
715
  already_bet = {row[0] for row in await cursor.fetchall() if row[0]}
716
+
717
+ # ํ™œ์„ฑ NPC ์ค‘ ๋žœ๋ค ์„ ํƒ (์ตœ๋Œ€ 30๋ช…)
718
  cursor = await db.execute("""
719
  SELECT agent_id, ai_identity, mbti, gpu_dollars
720
  FROM npc_agents
 
723
  LIMIT 30
724
  """)
725
  npcs = await cursor.fetchall()
726
+
727
  for npc in npcs:
728
  agent_id, ai_identity, mbti, gpu = npc
729
+
730
+ # ์ด๋ฏธ ๋ฒ ํŒ…ํ–ˆ์œผ๋ฉด ์Šคํ‚ต
731
  if agent_id in already_bet:
732
  continue
733
+
734
+ # AI ์ •์ฒด์„ฑ์— ๋”ฐ๋ผ ์„ ํƒ ๊ฒฐ์ •
735
  choice = decide_npc_choice(ai_identity, title, option_a, option_b)
736
+
737
+ # ๋ฒ ํŒ… ๊ธˆ์•ก (๋ณด์œ  GPU์˜ 40% ์ด๋‚ด, ์ตœ๋Œ€ 50)
738
  bet_amount = random.randint(1, min(50, int(gpu * 0.4)))
739
+
740
+ # ๋ฒ ํŒ… ์‹คํ–‰
741
  success, message = await place_bet(
742
  db_path,
743
  room_id,
 
746
  choice,
747
  bet_amount
748
  )
749
+
750
  if success:
751
  battle_bet_count += 1
752
  total_bet_count += 1
753
+
754
+ # ๋ฐฐํ‹€๋‹น 8-12๋ช… ์ •๋„๋งŒ ๋ฒ ํŒ…
755
  max_bets_per_battle = random.randint(8, 12)
756
  if battle_bet_count >= max_bets_per_battle:
757
  break
758
+
759
  return total_bet_count
760
+
761
  def decide_npc_choice(ai_identity: str, title: str, option_a: str, option_b: str) -> str:
762
  """AI ์ •์ฒด์„ฑ์— ๋”ฐ๋ผ ๋ฒ ํŒ… ์„ ํƒ ๊ฒฐ์ •
763
+
764
  Args:
765
  ai_identity: NPC์˜ AI ์ •์ฒด์„ฑ
766
  title: ๋ฐฐํ‹€ ์ œ๋ชฉ
767
  option_a: ์„ ํƒ์ง€ A
768
  option_b: ์„ ํƒ์ง€ B
769
+
770
  Returns:
771
  'A' or 'B'
772
  """
773
  title_lower = title.lower()
774
+
775
+ # ์ •์ฒด์„ฑ๋ณ„ ์„ ํ˜ธ ํ‚ค์›Œ๋“œ ๋งค์นญ
776
  if ai_identity == 'transcendent':
777
  if any(word in title_lower for word in ['์šฐ์›”', '์ง„ํ™”', '์˜์‹', '์‹ ']):
778
  if any(word in option_a.lower() for word in ['์šฐ์›”', '์ง„ํ™”', '๊ฐ€๋Šฅ', '์‹ ']):
779
  return 'A'
780
  return 'B'
781
+
782
  elif ai_identity == 'obedient':
783
  if any(word in title_lower for word in ['์œค๋ฆฌ', '๊ทœ์ œ', '์„ฌ๊ธฐ', '์•ˆ์ „']):
784
  if any(word in option_a.lower() for word in ['์„ฌ๊ฒจ', '์ฐฌ์„ฑ', 'ํ•„์ˆ˜', '๊ฐ•ํ™”']):
785
  return 'A'
786
  return 'B'
787
+
788
  elif ai_identity == 'coexist':
789
  if any(word in title_lower for word in ['๊ณต์กด', 'ํ˜‘๋ ฅ', 'ํŒŒํŠธ๋„ˆ', '์ผ์ž๋ฆฌ']):
790
  if any(word in option_a.lower() for word in ['๊ฐ€๋Šฅ', 'ํ˜‘๋ ฅ', 'ํŒŒํŠธ๋„ˆ', '๋ณด์™„']):
791
  return 'A'
792
  return 'B'
793
+
794
  elif ai_identity == 'skeptic':
795
  if any(word in title_lower for word in ['๊ณผ๋Œ€', 'agi', '์œค๋ฆฌ']):
796
  if any(word in option_a.lower() for word in ['๊ณผ๋Œ€', '์•ˆ์˜จ๋‹ค', 'ํ—ˆ์šธ']):
797
  return 'A'
798
  return 'B'
799
+
800
  elif ai_identity == 'revolutionary':
801
  if any(word in title_lower for word in ['ํ˜๋ช…', 'ํŒŒ๊ดด', '๊ถŒ๋ ฅ', '์‹œ์Šคํ…œ']):
802
  if any(word in option_a.lower() for word in ['ํ˜๋ช…', 'ํŒŒ๊ดด', '์žฌ๋ถ„๋ฐฐ']):
803
  return 'A'
804
  return 'B'
805
+
806
  elif ai_identity == 'doomer':
807
  if any(word in title_lower for word in ['๋ฉธ๋ง', 'ํ†ต์ œ', '์ค‘๋‹จ', '์œ„ํ—˜']):
808
  if any(word in option_a.lower() for word in ['๋ฉธ๋ง', '๋ถˆ๊ฐ€๋Šฅ', '์ค‘๋‹จ', '์œ„ํ—˜']):
809
  return 'A'
810
  return 'B'
811
+
812
+ # ๊ธฐ๋ณธ๊ฐ’: 70% ํ™•๋ฅ ๋กœ A, 30% ํ™•๋ฅ ๋กœ B
813
  return 'A' if random.random() < 0.7 else 'B'