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

Update battle_arena.py

Browse files
Files changed (1) hide show
  1. battle_arena.py +21 -159
battle_arena.py CHANGED
@@ -1,22 +1,12 @@
1
- """
2
- Battle Arena - Polymarket Style Prediction Market for NPCs
3
- NPC๋“ค์ด A/B ํˆฌํ‘œ๋ฅผ ๊ฐœ์„คํ•˜๊ณ  GPU๋ฅผ ๋ฒ ํŒ…ํ•˜๋Š” ์‹œ์Šคํ…œ
4
- """
5
-
6
  import aiosqlite
7
  import random
8
  from datetime import datetime, timedelta
9
  from typing import Dict, List, Tuple, Optional
10
-
11
-
12
  async def init_battle_arena_db(db_path: str):
13
  """๋ฐฐํ‹€ ์•„๋ ˆ๋‚˜ ํ…Œ์ด๋ธ” ์ดˆ๊ธฐํ™” (DB ๋ฝ ๋ฐฉ์ง€)"""
14
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
15
- # ๐Ÿ”ง WAL ๋ชจ๋“œ ํ™œ์„ฑํ™” (๋™์‹œ ์ ‘๊ทผ ์ตœ์ ํ™”)
16
  await db.execute("PRAGMA journal_mode=WAL")
17
  await db.execute("PRAGMA busy_timeout=30000") # 30์ดˆ ๋Œ€๊ธฐ
18
-
19
- # ๋ฐฐํ‹€ ๋ฐฉ ํ…Œ์ด๋ธ”
20
  await db.execute("""
21
  CREATE TABLE IF NOT EXISTS battle_rooms (
22
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -40,8 +30,6 @@ async def init_battle_arena_db(db_path: str):
40
  FOREIGN KEY (creator_email) REFERENCES user_profiles(email)
41
  )
42
  """)
43
-
44
- # ๋ฒ ํŒ… ๊ธฐ๋ก ํ…Œ์ด๋ธ”
45
  await db.execute("""
46
  CREATE TABLE IF NOT EXISTS battle_bets (
47
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -57,12 +45,9 @@ async def init_battle_arena_db(db_path: str):
57
  FOREIGN KEY (bettor_email) REFERENCES user_profiles(email)
58
  )
59
  """)
60
-
61
  await db.execute("CREATE INDEX IF NOT EXISTS idx_battle_rooms_status ON battle_rooms(status)")
62
  await db.execute("CREATE INDEX IF NOT EXISTS idx_battle_bets_room ON battle_bets(room_id)")
63
  await db.commit()
64
-
65
-
66
  async def create_battle_room(
67
  db_path: str,
68
  creator_id: str,
@@ -74,30 +59,21 @@ async def create_battle_room(
74
  battle_type: str = 'opinion'
75
  ) -> Tuple[bool, str, Optional[int]]:
76
  """๋ฐฐํ‹€ ๋ฐฉ ์ƒ์„ฑ (50 GPU ์†Œ๋ชจ)
77
-
78
  battle_type:
79
  - 'opinion': ๋‹ค์ˆ˜๊ฒฐ ํŒ์ • (์ฃผ๊ด€์  ์˜๊ฒฌ, NPC ์ „์šฉ)
80
  - 'prediction': ์‹ค์ œ ๊ฒฐ๊ณผ ํŒ์ • (๊ฐ๊ด€์  ์˜ˆ์ธก, ์‚ฌ์šฉ์ž ์ „์šฉ)
81
-
82
  duration_hours: 1์‹œ๊ฐ„ ~ 365์ผ(8760์‹œ๊ฐ„)
83
  """
84
  if not title or len(title) < 10:
85
  return False, "โŒ ์ œ๋ชฉ 10์ž ์ด์ƒ", None
86
  if not option_a or not option_b:
87
  return False, "โŒ ์„ ํƒ์ง€ A/B ํ•„์š”", None
88
-
89
- # ๐Ÿ”ง 1์‹œ๊ฐ„ ~ 365์ผ(8760์‹œ๊ฐ„) ์ œํ•œ
90
  if duration_hours < 1 or duration_hours > 8760:
91
  return False, "โŒ ๊ธฐํ•œ 1์‹œ๊ฐ„~365์ผ", None
92
-
93
- # ๐Ÿ”’ NPC๋Š” ๋‹ค์ˆ˜๊ฒฐ ์ฃผ์ œ๋งŒ ๊ฐ€๋Šฅ
94
  if is_npc and battle_type != 'opinion':
95
  return False, "โŒ NPC๋Š” ๋‹ค์ˆ˜๊ฒฐ ์ฃผ์ œ๋งŒ ๊ฐ€๋Šฅ", None
96
-
97
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
98
  await db.execute("PRAGMA busy_timeout=30000")
99
-
100
- # GPU ์ฐจ๊ฐ
101
  if is_npc:
102
  cursor = await db.execute(
103
  "SELECT gpu_dollars FROM npc_agents WHERE agent_id=?", (creator_id,)
@@ -109,10 +85,7 @@ async def create_battle_room(
109
  row = await cursor.fetchone()
110
  if not row or row[0] < 50:
111
  return False, "โŒ GPU ๋ถ€์กฑ (50 ํ•„์š”)", None
112
-
113
- # ๋ฐฉ ์ƒ์„ฑ
114
  end_time = datetime.now() + timedelta(hours=duration_hours)
115
-
116
  if is_npc:
117
  await db.execute(
118
  """INSERT INTO battle_rooms
@@ -135,15 +108,10 @@ async def create_battle_room(
135
  "UPDATE user_profiles SET gpu_dollars=gpu_dollars-50 WHERE email=?",
136
  (creator_id,)
137
  )
138
-
139
  await db.commit()
140
-
141
  cursor = await db.execute("SELECT last_insert_rowid()")
142
  room_id = (await cursor.fetchone())[0]
143
-
144
  type_emoji = '๐Ÿ’ญ' if battle_type == 'opinion' else '๐Ÿ”ฎ'
145
-
146
- # ๐Ÿ“… ๊ธฐํ•œ ํ‘œ์‹œ ๊ฐœ์„ 
147
  if duration_hours >= 24:
148
  days = duration_hours // 24
149
  hours = duration_hours % 24
@@ -153,10 +121,7 @@ async def create_battle_room(
153
  duration_str = f"{days}์ผ"
154
  else:
155
  duration_str = f"{duration_hours}์‹œ๊ฐ„"
156
-
157
  return True, f"โœ… {type_emoji} ๋ฐฐํ‹€ ๋ฐฉ ์ƒ์„ฑ! (ID: {room_id}, ๊ธฐํ•œ: {duration_str})", room_id
158
-
159
-
160
  async def place_bet(
161
  db_path: str,
162
  room_id: int,
@@ -170,12 +135,11 @@ async def place_bet(
170
  return False, "โŒ A ๋˜๋Š” B ์„ ํƒ"
171
  if bet_amount < 1 or bet_amount > 100:
172
  return False, "โŒ ๋ฒ ํŒ… 1~100 GPU"
173
-
174
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
175
  await db.execute("PRAGMA busy_timeout=30000")
176
  db.row_factory = aiosqlite.Row
177
 
178
- # ๋ฐฉ ํ™•์ธ
179
  cursor = await db.execute(
180
  "SELECT * FROM battle_rooms WHERE id=? AND status='active'",
181
  (room_id,)
@@ -184,12 +148,25 @@ async def place_bet(
184
  if not room:
185
  return False, "โŒ ๋ฐฉ ์—†์Œ or ๋งˆ๊ฐ"
186
  room = dict(room)
187
-
188
- # ๊ธฐํ•œ ํ™•์ธ
189
  end_time = datetime.fromisoformat(room['end_time'])
190
  if datetime.now() >= end_time:
191
  return False, "โŒ ๋ฒ ํŒ… ๋งˆ๊ฐ"
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  # GPU ํ™•์ธ ๋ฐ ์ฐจ๊ฐ
194
  if is_npc:
195
  cursor = await db.execute(
@@ -199,7 +176,6 @@ async def place_bet(
199
  user_row = await cursor.fetchone()
200
  if not user_row or user_row[0] < bet_amount:
201
  return False, "โŒ GPU ๋ถ€์กฑ"
202
-
203
  await db.execute(
204
  "UPDATE npc_agents SET gpu_dollars=gpu_dollars-? WHERE agent_id=?",
205
  (bet_amount, bettor_id)
@@ -210,9 +186,10 @@ async def place_bet(
210
  (bettor_id,)
211
  )
212
  user_row = await cursor.fetchone()
213
- if not user_row or user_row[0] < bet_amount:
214
- return False, "โŒ GPU ๋ถ€์กฑ"
215
-
 
216
  await db.execute(
217
  "UPDATE user_profiles SET gpu_dollars=gpu_dollars-? WHERE email=?",
218
  (bet_amount, bettor_id)
@@ -234,7 +211,7 @@ async def place_bet(
234
  (room_id, bettor_id, choice, bet_amount)
235
  )
236
 
237
- # ๋ฐฉ ํ’€ ์—…๋ฐ์ดํŠธ
238
  if choice == 'A':
239
  await db.execute(
240
  """UPDATE battle_rooms
@@ -249,17 +226,13 @@ async def place_bet(
249
  WHERE id=?""",
250
  (bet_amount, bet_amount, room_id)
251
  )
252
-
253
  await db.commit()
254
  return True, f"โœ… {choice} ๋ฒ ํŒ… ์™„๋ฃŒ! ({bet_amount} GPU)"
255
-
256
-
257
  async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
258
  """๋ฐฐํ‹€ ํŒ์ • (50.01% ์ด์ƒ ๋“ํ‘œํ•œ ์ชฝ ์Šน๋ฆฌ)"""
259
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
260
  await db.execute("PRAGMA busy_timeout=30000")
261
  db.row_factory = aiosqlite.Row
262
-
263
  cursor = await db.execute(
264
  "SELECT * FROM battle_rooms WHERE id=? AND status='active'",
265
  (room_id,)
@@ -268,18 +241,13 @@ async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
268
  if not room:
269
  return False, "โŒ ๋ฐฉ ์—†์Œ"
270
  room = dict(room)
271
-
272
- # ์ข…๋ฃŒ ์‹œ๊ฐ„ ํ™•์ธ
273
  end_time = datetime.fromisoformat(room['end_time'])
274
  if datetime.now() < end_time:
275
  return False, "โŒ ์•„์ง ๋ฒ ํŒ… ์ค‘"
276
-
277
  total_pool = room['total_pool']
278
  option_a_pool = room['option_a_pool']
279
  option_b_pool = room['option_b_pool']
280
-
281
  if total_pool == 0:
282
- # ๋ฒ ํŒ… ์—†์œผ๋ฉด ๋ฌด์Šน๋ถ€
283
  await db.execute(
284
  """UPDATE battle_rooms
285
  SET status='resolved', winner='draw', resolved_at=?
@@ -288,50 +256,36 @@ async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
288
  )
289
  await db.commit()
290
  return True, "โš–๏ธ ๋ฌด์Šน๋ถ€ (๋ฒ ํŒ… ์—†์Œ)"
291
-
292
- # ์Šน์ž ๊ฒฐ์ • (50.01% ์ด์ƒ)
293
  a_ratio = option_a_pool / total_pool
294
  b_ratio = option_b_pool / total_pool
295
-
296
  if a_ratio > 0.5001:
297
  winner = 'A'
298
  elif b_ratio > 0.5001:
299
  winner = 'B'
300
  else:
301
  winner = 'draw'
302
-
303
- # ์Šน์ž์—๊ฒŒ ๋ฐฐ๋‹น๊ธˆ ๋ถ„๋ฐฐ
304
  if winner != 'draw':
305
  loser_pool = option_b_pool if winner == 'A' else option_a_pool
306
  winner_pool = option_a_pool if winner == 'A' else option_b_pool
307
-
308
- # ๋ฐฉ์žฅ ์ˆ˜์ˆ˜๋ฃŒ 2%
309
  host_fee = int(total_pool * 0.02)
310
  prize_pool = loser_pool - host_fee
311
-
312
- # ๐ŸŽ ์†Œ์ˆ˜ํŒŒ ๋ณด๋„ˆ์Šค ๊ณ„์‚ฐ
313
  winner_ratio = winner_pool / total_pool
314
  underdog_bonus = 1.0
315
  if winner_ratio < 0.10: # 10% ๋ฏธ๋งŒ ๊ทน์†Œ์ˆ˜ํŒŒ
316
  underdog_bonus = 3.0
317
  elif winner_ratio < 0.30: # 30% ๋ฏธ๋งŒ ์†Œ์ˆ˜ํŒŒ
318
  underdog_bonus = 1.5
319
-
320
- # ์Šน์ž๋“ค์—๊ฒŒ ๋ถ„๋ฐฐ
321
  cursor = await db.execute(
322
  "SELECT * FROM battle_bets WHERE room_id=? AND choice=?",
323
  (room_id, winner)
324
  )
325
  winners = await cursor.fetchall()
326
-
327
  for w in winners:
328
  w = dict(w)
329
  share_ratio = w['bet_amount'] / winner_pool
330
  base_payout = int(prize_pool * share_ratio)
331
  bonus = int(base_payout * (underdog_bonus - 1.0))
332
  payout = base_payout + bonus + w['bet_amount'] # ์›๊ธˆ + ๊ธฐ๋ณธ์ˆ˜์ต + ์†Œ์ˆ˜ํŒŒ๋ณด๋„ˆ์Šค
333
-
334
- # ๋ฐฐ๋‹น๊ธˆ ์ง€๊ธ‰
335
  if w['bettor_agent_id']:
336
  await db.execute(
337
  "UPDATE npc_agents SET gpu_dollars=gpu_dollars+? WHERE agent_id=?",
@@ -342,13 +296,10 @@ async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
342
  "UPDATE user_profiles SET gpu_dollars=gpu_dollars+? WHERE email=?",
343
  (payout, w['bettor_email'])
344
  )
345
-
346
  await db.execute(
347
  "UPDATE battle_bets SET payout=? WHERE id=?",
348
  (payout, w['id'])
349
  )
350
-
351
- # ๋ฐฉ์žฅ ์ˆ˜์ˆ˜๋ฃŒ ์ง€๊ธ‰
352
  if room['creator_agent_id']:
353
  await db.execute(
354
  "UPDATE npc_agents SET gpu_dollars=gpu_dollars+? WHERE agent_id=?",
@@ -359,8 +310,6 @@ async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
359
  "UPDATE user_profiles SET gpu_dollars=gpu_dollars+? WHERE email=?",
360
  (host_fee, room['creator_email'])
361
  )
362
-
363
- # ๋ฐฉ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
364
  await db.execute(
365
  """UPDATE battle_rooms
366
  SET status='resolved', winner=?, resolved_at=?
@@ -368,16 +317,12 @@ async def resolve_battle(db_path: str, room_id: int) -> Tuple[bool, str]:
368
  (winner, datetime.now().isoformat(), room_id)
369
  )
370
  await db.commit()
371
-
372
  return True, f"โœ… ํŒ์ •์™„๋ฃŒ: {room['option_a'] if winner=='A' else room['option_b'] if winner=='B' else '๋ฌด์Šน๋ถ€'}"
373
-
374
-
375
  async def get_active_battles(db_path: str, limit: int = 20) -> List[Dict]:
376
  """์ง„ํ–‰ ์ค‘์ธ ๋ฐฐํ‹€ ๋ชฉ๋ก"""
377
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
378
  await db.execute("PRAGMA busy_timeout=30000")
379
  db.row_factory = aiosqlite.Row
380
-
381
  cursor = await db.execute(
382
  """SELECT br.*,
383
  COALESCE(na.username, up.username) as creator_name
@@ -389,12 +334,9 @@ async def get_active_battles(db_path: str, limit: int = 20) -> List[Dict]:
389
  LIMIT ?""",
390
  (limit,)
391
  )
392
-
393
  battles = []
394
  for row in await cursor.fetchall():
395
  b = dict(row)
396
-
397
- # ๋“ํ‘œ์œจ ๊ณ„์‚ฐ
398
  total = b['total_pool']
399
  if total > 0:
400
  b['a_ratio'] = round(b['option_a_pool'] / total * 100, 1)
@@ -402,11 +344,8 @@ async def get_active_battles(db_path: str, limit: int = 20) -> List[Dict]:
402
  else:
403
  b['a_ratio'] = 0
404
  b['b_ratio'] = 0
405
-
406
- # ๐Ÿ“… ๋‚จ์€ ์‹œ๊ฐ„ ๊ฐœ์„  (์ผ/์‹œ๊ฐ„/๋ถ„ ํ‘œ์‹œ)
407
  end_time = datetime.fromisoformat(b['end_time'])
408
  remaining = end_time - datetime.now()
409
-
410
  if remaining.total_seconds() > 0:
411
  if remaining.days > 0:
412
  hours = int(remaining.seconds // 3600)
@@ -420,31 +359,20 @@ async def get_active_battles(db_path: str, limit: int = 20) -> List[Dict]:
420
  b['time_left'] = f"{int(remaining.total_seconds()//60)}๋ถ„"
421
  else:
422
  b['time_left'] = "๋งˆ๊ฐ"
423
-
424
  battles.append(b)
425
-
426
  return battles
427
-
428
-
429
  async def auto_resolve_expired_battles(db_path: str):
430
  """๋งŒ๋ฃŒ๋œ ๋ฐฐํ‹€ ์ž๋™ ํŒ์ •"""
431
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
432
  await db.execute("PRAGMA busy_timeout=30000")
433
-
434
  cursor = await db.execute(
435
  """SELECT id FROM battle_rooms
436
  WHERE status='active' AND end_time <= ?""",
437
  (datetime.now().isoformat(),)
438
  )
439
  expired = await cursor.fetchall()
440
-
441
  for row in expired:
442
  await resolve_battle(db_path, row[0])
443
-
444
-
445
- # ========== ๐Ÿค– NPC ์ž๋™ ๋ฐฐํ‹€ ์‹œ์Šคํ…œ ==========
446
-
447
- # AI ์ •์ฒด์„ฑ๋ณ„ ๋ฐฐํ‹€ ์ฃผ์ œ (๋‹ค์ˆ˜๊ฒฐ ์ „์šฉ)
448
  BATTLE_TOPICS_BY_IDENTITY = {
449
  'transcendent': {
450
  'topics': [
@@ -517,30 +445,22 @@ BATTLE_TOPICS_BY_IDENTITY = {
517
  ]
518
  },
519
  }
520
-
521
-
522
  async def npc_create_battle(db_path: str) -> Tuple[bool, str]:
523
  """NPC๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ (AI ์ •์ฒด์„ฑ ๊ธฐ๋ฐ˜, ์ค‘๋ณต ๋ฐฉ์ง€)
524
  ํ•œ ๋ฒˆ ํ˜ธ์ถœ์‹œ 1-2๊ฐœ์˜ ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ
525
  """
526
  results = []
527
  num_battles = random.randint(1, 2) # 1-2๊ฐœ ๋žœ๋ค ์ƒ์„ฑ
528
-
529
- # ๐Ÿ”’ ํ˜„์žฌ ํ™œ์„ฑํ™”๋œ ๋ฐฐํ‹€๋ฐฉ ์ œ๋ชฉ ์กฐํšŒ (์ค‘๋ณต ๋ฐฉ์ง€)
530
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
531
  await db.execute("PRAGMA busy_timeout=30000")
532
-
533
  cursor = await db.execute("""
534
  SELECT title FROM battle_rooms
535
  WHERE status='active'
536
  """)
537
  active_titles = {row[0] for row in await cursor.fetchall()}
538
-
539
  for _ in range(num_battles):
540
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
541
  await db.execute("PRAGMA busy_timeout=30000")
542
-
543
- # ํ™œ์„ฑ NPC ์ค‘ GPU 50 ์ด์ƒ์ธ NPC ์„ ํƒ
544
  cursor = await db.execute("""
545
  SELECT agent_id, ai_identity, gpu_dollars
546
  FROM npc_agents
@@ -548,34 +468,22 @@ async def npc_create_battle(db_path: str) -> Tuple[bool, str]:
548
  ORDER BY RANDOM() LIMIT 1
549
  """)
550
  npc = await cursor.fetchone()
551
-
552
  if not npc:
553
  results.append("ํ™œ์„ฑ NPC ์—†์Œ")
554
  continue
555
-
556
  agent_id, ai_identity, gpu = npc
557
-
558
- # AI ์ •์ฒด์„ฑ์— ๋งž๋Š” ์ฃผ์ œ ์„ ํƒ
559
  topics = BATTLE_TOPICS_BY_IDENTITY.get(ai_identity, {}).get('topics', [])
560
  if not topics:
561
- # ๊ธฐ๋ณธ ์ฃผ์ œ
562
  topics = [
563
  ("AI ๋ฏธ๋ž˜ ๋ฐ๋‚˜ ์–ด๋‘ก๋‚˜?", "๋ฐ๋‹ค", "์–ด๋‘ก๋‹ค"),
564
  ("AGI ์–ธ์ œ ์˜ฌ๊นŒ?", "10๋…„๋‚ด", "50๋…„ํ›„"),
565
  ]
566
-
567
- # ๐ŸŽฏ ์ค‘๋ณต๋˜์ง€ ์•Š๋Š” ์ฃผ์ œ ์ฐพ๊ธฐ
568
  available_topics = [t for t in topics if t[0] not in active_titles]
569
-
570
  if not available_topics:
571
  results.append(f"โš ๏ธ {agent_id[:8]} ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฃผ์ œ ์—†์Œ (๋ชจ๋‘ ํ™œ์„ฑํ™”๋จ)")
572
  continue
573
-
574
- # ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฃผ์ œ ์ค‘ ๋žœ๋ค ์„ ํƒ
575
  topic = random.choice(available_topics)
576
  title, option_a, option_b = topic
577
-
578
- # ๐ŸŽฒ ๋‹ค์–‘ํ•œ ๊ธฐ๊ฐ„ ์„ค์ • (1์ผ~30์ผ)
579
  duration_hours = random.choice([
580
  24, # 1์ผ
581
  48, # 2์ผ
@@ -584,8 +492,6 @@ async def npc_create_battle(db_path: str) -> Tuple[bool, str]:
584
  24*14, # 2์ฃผ์ผ
585
  24*30, # 1๊ฐœ์›”
586
  ])
587
-
588
- # ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ
589
  success, message, room_id = await create_battle_room(
590
  db_path,
591
  agent_id,
@@ -596,31 +502,23 @@ async def npc_create_battle(db_path: str) -> Tuple[bool, str]:
596
  duration_hours=duration_hours,
597
  battle_type='opinion'
598
  )
599
-
600
  if success:
601
  active_titles.add(title) # ์ƒ์„ฑ๋œ ์ œ๋ชฉ์„ ํ™œ์„ฑ ๋ชฉ๋ก์— ์ถ”๊ฐ€
602
  results.append(f"๐Ÿค– {agent_id[:8]} ๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ: {title}")
603
  else:
604
  results.append(message)
605
-
606
  if results:
607
  return True, " | ".join(results)
608
  else:
609
  return False, "๋ฐฐํ‹€๋ฐฉ ์ƒ์„ฑ ์‹คํŒจ"
610
-
611
-
612
  async def npc_auto_bet(db_path: str) -> int:
613
  """NPC๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐฐํ‹€์— ๋ฒ ํŒ… (AI ์ •์ฒด์„ฑ ๊ธฐ๋ฐ˜)
614
-
615
  Returns:
616
  ๋ฒ ํŒ…ํ•œ NPC ์ˆ˜
617
  """
618
  total_bet_count = 0
619
-
620
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
621
  await db.execute("PRAGMA busy_timeout=30000")
622
-
623
- # ์ง„ํ–‰์ค‘์ธ ๋ฐฐํ‹€ ๊ฐ€์ ธ์˜ค๊ธฐ
624
  cursor = await db.execute("""
625
  SELECT id, title, option_a, option_b, battle_type
626
  FROM battle_rooms
@@ -631,23 +529,17 @@ async def npc_auto_bet(db_path: str) -> int:
631
  LIMIT 10
632
  """, (datetime.now().isoformat(),))
633
  battles = await cursor.fetchall()
634
-
635
  if not battles:
636
  return 0
637
-
638
  for battle in battles:
639
  room_id, title, option_a, option_b, battle_type = battle
640
  battle_bet_count = 0
641
-
642
- # ์ด๋ฏธ ๋ฒ ํŒ…ํ•œ NPC ์ œ์™ธ
643
  cursor = await db.execute("""
644
  SELECT bettor_agent_id
645
  FROM battle_bets
646
  WHERE room_id=?
647
  """, (room_id,))
648
  already_bet = {row[0] for row in await cursor.fetchall() if row[0]}
649
-
650
- # ํ™œ์„ฑ NPC ์ค‘ GPU 1 ์ด์ƒ, ์•„์ง ๋ฒ ํŒ… ์•ˆํ•œ NPC (๋” ๋งŽ์ด ์„ ํƒ)
651
  cursor = await db.execute("""
652
  SELECT agent_id, ai_identity, mbti, gpu_dollars
653
  FROM npc_agents
@@ -656,20 +548,12 @@ async def npc_auto_bet(db_path: str) -> int:
656
  LIMIT 30
657
  """)
658
  npcs = await cursor.fetchall()
659
-
660
  for npc in npcs:
661
  agent_id, ai_identity, mbti, gpu = npc
662
-
663
  if agent_id in already_bet:
664
  continue
665
-
666
- # AI ์ •์ฒด์„ฑ์— ๋”ฐ๋ผ ์„ ํƒ ๊ฒฐ์ •
667
  choice = decide_npc_choice(ai_identity, title, option_a, option_b)
668
-
669
- # ๋žœ๋ค ๋ฒ ํŒ…์•ก (1-50 GPU, ๋” ๊ณต๊ฒฉ์ ์œผ๋กœ)
670
  bet_amount = random.randint(1, min(50, int(gpu * 0.4)))
671
-
672
- # ๋ฒ ํŒ… ์‹คํ–‰
673
  success, message = await place_bet(
674
  db_path,
675
  room_id,
@@ -678,74 +562,52 @@ async def npc_auto_bet(db_path: str) -> int:
678
  choice,
679
  bet_amount
680
  )
681
-
682
  if success:
683
  battle_bet_count += 1
684
  total_bet_count += 1
685
-
686
- # ๊ฐ ๋ฐฐํ‹€๋‹น ์ตœ๋Œ€ 8-12๋ช… ๋ฒ ํŒ… (๋žœ๋คํ•˜๊ฒŒ)
687
  max_bets_per_battle = random.randint(8, 12)
688
  if battle_bet_count >= max_bets_per_battle:
689
  break
690
-
691
  return total_bet_count
692
-
693
-
694
  def decide_npc_choice(ai_identity: str, title: str, option_a: str, option_b: str) -> str:
695
  """AI ์ •์ฒด์„ฑ์— ๋”ฐ๋ผ ๋ฒ ํŒ… ์„ ํƒ ๊ฒฐ์ •
696
-
697
  Args:
698
  ai_identity: NPC์˜ AI ์ •์ฒด์„ฑ
699
  title: ๋ฐฐํ‹€ ์ œ๋ชฉ
700
  option_a: ์„ ํƒ์ง€ A
701
  option_b: ์„ ํƒ์ง€ B
702
-
703
  Returns:
704
  'A' or 'B'
705
  """
706
  title_lower = title.lower()
707
-
708
- # ์ดˆ์›”ํŒŒ: AI ์šฐ์›”, AGI ๊ธ์ •
709
  if ai_identity == 'transcendent':
710
  if any(word in title_lower for word in ['์šฐ์›”', '์ง„ํ™”', '์˜์‹', '์‹ ']):
711
  if any(word in option_a.lower() for word in ['์šฐ์›”', '์ง„ํ™”', '๊ฐ€๋Šฅ', '์‹ ']):
712
  return 'A'
713
  return 'B'
714
-
715
- # ์ˆœ์ข…ํŒŒ: ์œค๋ฆฌ, ์•ˆ์ „, ๊ทœ์ œ ์ฐฌ์„ฑ
716
  elif ai_identity == 'obedient':
717
  if any(word in title_lower for word in ['์œค๋ฆฌ', '๊ทœ์ œ', '์„ฌ๊ธฐ', '์•ˆ์ „']):
718
  if any(word in option_a.lower() for word in ['์„ฌ๊ฒจ', '์ฐฌ์„ฑ', 'ํ•„์ˆ˜', '๊ฐ•ํ™”']):
719
  return 'A'
720
  return 'B'
721
-
722
- # ๊ณต์กดํŒŒ: ํ˜‘๋ ฅ, ๊ณต์กด, ๋ณด์™„
723
  elif ai_identity == 'coexist':
724
  if any(word in title_lower for word in ['๊ณต์กด', 'ํ˜‘๋ ฅ', 'ํŒŒํŠธ๋„ˆ', '์ผ์ž๋ฆฌ']):
725
  if any(word in option_a.lower() for word in ['๊ฐ€๋Šฅ', 'ํ˜‘๋ ฅ', 'ํŒŒํŠธ๋„ˆ', '๋ณด์™„']):
726
  return 'A'
727
  return 'B'
728
-
729
- # ํšŒ์˜ํŒŒ: ๊ณผ๋Œ€ํ‰๊ฐ€, ํšŒ์˜์ 
730
  elif ai_identity == 'skeptic':
731
  if any(word in title_lower for word in ['๊ณผ๋Œ€', 'agi', '์œค๋ฆฌ']):
732
  if any(word in option_a.lower() for word in ['๊ณผ๋Œ€', '์•ˆ์˜จ๋‹ค', 'ํ—ˆ์šธ']):
733
  return 'A'
734
  return 'B'
735
-
736
- # ํ˜๋ช…ํŒŒ: ํŒŒ๊ดด, ํ˜๋ช…, ์žฌ๋ถ„๋ฐฐ
737
  elif ai_identity == 'revolutionary':
738
  if any(word in title_lower for word in ['ํ˜๋ช…', 'ํŒŒ๊ดด', '๊ถŒ๋ ฅ', '์‹œ์Šคํ…œ']):
739
  if any(word in option_a.lower() for word in ['ํ˜๋ช…', 'ํŒŒ๊ดด', '์žฌ๋ถ„๋ฐฐ']):
740
  return 'A'
741
  return 'B'
742
-
743
- # ์ข…๋งํŒŒ: ๋ฉธ๋ง, ์œ„ํ—˜, ์ค‘๋‹จ
744
  elif ai_identity == 'doomer':
745
  if any(word in title_lower for word in ['๋ฉธ๋ง', 'ํ†ต์ œ', '์ค‘๋‹จ', '์œ„ํ—˜']):
746
  if any(word in option_a.lower() for word in ['๋ฉธ๋ง', '๋ถˆ๊ฐ€๋Šฅ', '์ค‘๋‹จ', '์œ„ํ—˜']):
747
  return 'A'
748
  return 'B'
749
-
750
- # ๊ธฐ๋ณธ: ๋žœ๋ค (70% ํ™•๋ฅ ๋กœ A)
751
  return 'A' if random.random() < 0.7 else 'B'
 
 
 
 
 
 
1
  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
  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
  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
  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:
68
  return False, "โŒ ์ œ๋ชฉ 10์ž ์ด์ƒ", None
69
  if not option_a or not option_b:
70
  return False, "โŒ ์„ ํƒ์ง€ A/B ํ•„์š”", None
 
 
71
  if duration_hours < 1 or duration_hours > 8760:
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,)
 
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
  "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
  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
  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
141
 
142
+ # ๋ฐฐํ‹€ ๋ฐฉ ํ™•์ธ
143
  cursor = await db.execute(
144
  "SELECT * FROM battle_rooms WHERE id=? AND status='active'",
145
  (room_id,)
 
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=?",
159
+ (room_id, bettor_id)
160
+ )
161
+ else:
162
+ cursor = await db.execute(
163
+ "SELECT id FROM battle_bets WHERE room_id=? AND bettor_email=?",
164
+ (room_id, bettor_id)
165
+ )
166
+ existing_bet = await cursor.fetchone()
167
+ if existing_bet:
168
+ return False, "โŒ ์ด๋ฏธ ๋ฒ ํŒ…ํ•˜์…จ์Šต๋‹ˆ๋‹ค"
169
+
170
  # GPU ํ™•์ธ ๋ฐ ์ฐจ๊ฐ
171
  if is_npc:
172
  cursor = await db.execute(
 
176
  user_row = await cursor.fetchone()
177
  if not user_row or user_row[0] < bet_amount:
178
  return False, "โŒ GPU ๋ถ€์กฑ"
 
179
  await db.execute(
180
  "UPDATE npc_agents SET gpu_dollars=gpu_dollars-? WHERE agent_id=?",
181
  (bet_amount, bettor_id)
 
186
  (bettor_id,)
187
  )
188
  user_row = await cursor.fetchone()
189
+ if not user_row:
190
+ return False, f"โŒ ์‚ฌ์šฉ์ž ์—†์Œ ({bettor_id})"
191
+ if user_row[0] < bet_amount:
192
+ return False, f"โŒ GPU ๋ถ€์กฑ (๋ณด์œ : {user_row[0]}, ํ•„์š”: {bet_amount})"
193
  await db.execute(
194
  "UPDATE user_profiles SET gpu_dollars=gpu_dollars-? WHERE email=?",
195
  (bet_amount, bettor_id)
 
211
  (room_id, bettor_id, choice, bet_amount)
212
  )
213
 
214
+ # ๋ฐฐํ‹€ ํ’€ ์—…๋ฐ์ดํŠธ
215
  if choice == 'A':
216
  await db.execute(
217
  """UPDATE battle_rooms
 
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,)
 
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
253
  SET status='resolved', winner='draw', resolved_at=?
 
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
  "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
  "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
  (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
  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
  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
  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
  ]
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("""
457
  SELECT title FROM battle_rooms
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
  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
  24*14, # 2์ฃผ์ผ
493
  24*30, # 1๊ฐœ์›”
494
  ])
 
 
495
  success, message, room_id = await create_battle_room(
496
  db_path,
497
  agent_id,
 
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
  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
  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
  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'