96 lines
3.5 KiB
Python
96 lines
3.5 KiB
Python
"""Python 引擎冒煙測試(不連網、不呼叫真實 LLM)。
|
||
|
||
以「模擬 LLM」取代 API 呼叫,跑完整場騙子酒館遊戲,驗證
|
||
game.py / player.py / game_record.py 主流程可正常完成並產生記錄。
|
||
使用方式:
|
||
.venv\\Scripts\\python.exe scripts\\smoke_test_python.py
|
||
(若主控台遇到簡體中文編碼錯誤,請先設定 $env:PYTHONIOENCODING="utf-8")
|
||
"""
|
||
import os
|
||
import sys
|
||
import random
|
||
|
||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
sys.path.insert(0, REPO_ROOT)
|
||
|
||
from game import Game
|
||
|
||
|
||
class MockChat:
|
||
"""附著在某位玩家身上的假 LLM:依提示內容回傳格式正確的 JSON。
|
||
注意:chat() 統一回傳 (content, reasoning_content) 二元素 tuple。
|
||
"""
|
||
|
||
def __init__(self, player):
|
||
self.player = player
|
||
self.decision_count = 0
|
||
|
||
def chat(self, messages, model=None):
|
||
prompt = messages[0]["content"] if messages else ""
|
||
if "played_cards" in prompt:
|
||
return self._play_reply(), ""
|
||
if "was_challenged" in prompt:
|
||
return self._challenge_reply(), ""
|
||
return "mock opinion: \u8a72\u73a9\u5bb6\u76ee\u524d\u7121\u660e\u986f\u50be\u5411\u3002", ""
|
||
|
||
def _play_reply(self):
|
||
"""\u7d66\u4e86\u81ea\u5df1\u7684\u624b\u724c\uff1a\u540c\u9ede\u6578 >=2 \u51fa\u6700\u591a 3 \u5f35\uff0c\u5426\u5247\u51fa 1 \u5f35\u3002"""
|
||
hand = sorted(self.player.hand)
|
||
cards = [hand[0]]
|
||
for c in hand[1:3]:
|
||
if c == cards[0]:
|
||
cards.append(c)
|
||
cards_json = ",".join('"%s"' % c for c in cards)
|
||
return ('{"played_cards": [%s], "behavior": "\u7a69\u5065", '
|
||
'"play_reason": "mock heuristic"}' % cards_json)
|
||
|
||
def _challenge_reply(self):
|
||
"""\u6bcf 4 \u6b21\u8cea\u7591\u6c7a\u7b56\u5c31\u8cea\u7591\u4e00\u6b21\uff0c\u8986\u84cb\u6210\u529f/\u5931\u6557\u5169\u689d\u8def\u5f91\u3002"""
|
||
self.decision_count += 1
|
||
do_challenge = (self.decision_count % 4 == 0)
|
||
return ('{"was_challenged": %s, "challenge_reason": "mock trigger"}' %
|
||
("true" if do_challenge else "false"))
|
||
|
||
|
||
def main():
|
||
random.seed(20260803)
|
||
player_configs = [
|
||
{"name": "DeepSeek", "model": "mock"},
|
||
{"name": "GPT", "model": "mock"},
|
||
{"name": "Claude", "model": "mock"},
|
||
{"name": "Gemini", "model": "mock"},
|
||
]
|
||
|
||
game = Game(player_configs)
|
||
for p in game.players:
|
||
p.llm_client = MockChat(p)
|
||
|
||
max_plays = 400
|
||
for step in range(max_plays):
|
||
if game.game_over:
|
||
break
|
||
game.play_round()
|
||
|
||
if game.game_over:
|
||
print("[OK] \u904a\u6232\u7d50\u675f\uff0c\u4e3b\u6d41\u7a0b\u53ef\u6b63\u5e38\u5b8c\u6210\u3002")
|
||
else:
|
||
print(f"[FAIL] \u8dd1\u4e86 {max_plays} \u6b65\u4ecd\u672a\u7d50\u675f\uff08\u53ef\u80fd\u89c4\u5247\u6709\u6b7b\u7d50\uff09\u3002 rounds={game.round_count}")
|
||
return 1
|
||
|
||
rec = game.game_record
|
||
rounds = len(rec.rounds)
|
||
alive = [p.name for p in game.players if p.alive]
|
||
print(f"[OK] \u7372\u52dd\u8005={rec.winner} | \u5b58\u6d3b\u73a9\u5bb6={alive}")
|
||
print(f"[OK] \u8a18\u9304\u56de\u5408\u6578={rounds}")
|
||
|
||
import glob
|
||
files = glob.glob(os.path.join(REPO_ROOT, "game_records", "*.json"))
|
||
print(f"[OK] game_records \u7522\u51fa {len(files)} \u500b JSON \u8a18\u9304\u6a94")
|
||
if files:
|
||
newest = max(files, key=os.path.getmtime)
|
||
print(f" \u6700\u65b0\u8a18\u9304: {os.path.basename(newest)}")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main()) |