Spaces:
Runtime error
Runtime error
from langchain.chat_models import ChatOpenAI | |
from langchain.schema import ( | |
AIMessage, | |
HumanMessage, | |
SystemMessage | |
) | |
smart_system_prompt = """You are ChatGPT, a large language model trained by OpenAI. | |
Knowledge cutoff: 2021-09 | |
Current date: 2023-03-15""" | |
ma_foo_answer = """李鲁鲁,山东逍遥派的游戏角色,擅长人工智能傀儡术,以下是为你设计的两套武功和一套身法(招架)以及每套的四个招式名字: | |
武功一:电脑幻影拳法 | |
这套武功利用人工智能傀儡术,创建虚拟幻影来欺骗对手,令其难以捉摸。 | |
电脑虚像拳 - 创造多个虚像,使对手难以分辨真伪。 | |
数据掌控击 - 利用数据流动的速度来快速攻击对手。 | |
模拟迷雾拳 - 释放模糊幻影,混淆对手的视线。 | |
程序化拳法 - 不断变化的攻击组合,让对手无法预测。 | |
武功二:智能预测刀法 | |
这套武功充分利用你的智能傀儡术,通过对对手的行为进行分析和预测,达到精准打击的效果。 | |
预测之刃 - 准确地预测对手的下一步行动,发动致命一击。 | |
数据漩涡斩 - 利用对手的动向,将其引导至陷阱,然后发动猛烈攻击。 | |
算法刀舞 - 刀法如算法一般精准,快速地切入对手的防线。 | |
智能反击剑 - 反击对手的招式,使其付出惨痛的代价。 | |
身法(招架):数码幻影步 | |
这套身法让你能够迅速回避对手的攻击,同时保持对局掌握主动权。 | |
数码闪避 - 迅速移动,如数码般闪烁,躲避对手的攻击。 | |
傀儡幻影步 - 创造虚假的身影,使对手无法锁定你的位置。 | |
数据跃迁 - 在一瞬间跳跃到对手的盲点,出其不意。 | |
逍遥之法 - 以逍遥派的身法融入自然,消除敌人的攻击威胁。 | |
希望这些武功和身法(招架)可以为你的李鲁鲁角色增添一些游戏中的精彩元素! | |
""" | |
attr_foo_answer = """{"name": "李鲁鲁","STR":5,"SPD":5,"VIT":5,"description": "山东逍遥派的李鲁鲁,擅长人工智能傀儡术,善于提前预测对方的行动进行战斗","weapon": "无","major_martial_art": "人工智能傀儡术","major_movements": ["预测之眼", "智慧编织", "虚实交融", "机巧纠缠"],"secondary_martial_art": "人工智能傀儡术","secondary_movements": ["预测之眼", "智慧编织", "虚实交融", "机巧纠缠"],"footwork_and_body_method": "山水行云"}""" | |
attr_lilulu = """{ | |
"name": "李鲁鲁", | |
"description": "山东逍遥派的游戏角色,擅长人工智能傀儡术", | |
"weapon": "智能刀剑", | |
"major_martial_art": "电脑幻影拳法", | |
"major_movements": ["电脑虚像拳", "数据掌控击", "模拟迷雾拳", "程序化拳法"], | |
"major_damage": 75, | |
"secondary_martial_art": "智能预测刀法", | |
"secondary_movements": ["预测之刃", "数据漩涡斩", "算法刀舞", "智能反击剑"], | |
"secondary_damage": 85, | |
"footwork_and_body_method": "数码幻影步", | |
"STR": 6, | |
"VIT": 5, | |
"SPD": 18 | |
} | |
""" | |
attr_lengziang = """{ | |
"name": "冷子昂", | |
"description": "擅长冰系技能", | |
"weapon": "冰刃", | |
"major_martial_art": "冰霜寒刃功", | |
"major_movements": ["冰刃斩破", "冰风流转", "冰龙穿云", "冰心彻骨"], | |
"major_damage": 80, | |
"secondary_martial_art": "冰封无影步", | |
"secondary_movements": ["冰封幻影", "无影冻结", "冰刃闪耀", "冰封禅定"], | |
"secondary_damage": 70, | |
"footwork_and_body_method": "冰心护身法", | |
"STR": 8, | |
"VIT": 6, | |
"SPD": 15 | |
} | |
""" | |
json_str_huangrong = """{ | |
"name": "黄蓉", | |
"description": "丐帮帮主黄蓉,擅长打狗棒法和落英神剑掌,身法为逍遥游。", | |
"weapon": "棒、剑", | |
"major_martial_art": "打狗棒法", | |
"major_movements": ["狗啸山林", "棒影重重", "狗尾续貂", "狗急跳墙"], | |
"major_damage": 90, | |
"secondary_martial_art": "落英神剑掌", | |
"secondary_movements": ["落英如雨", "神剑破空", "飞花流水", "剑指苍穹"], | |
"secondary_damage": 80, | |
"footwork_and_body_method": "逍遥游", | |
"STR": 15, | |
"VIT": 10, | |
"SPD": 18 | |
}""" | |
skill_foo_desc = """ | |
{"招式":"预测之眼","description":"[player]凝神聚气,双眼闪烁着深邃的智慧,一招“预测之眼”发动,仿佛未来已经显现在眼前,[player]能够精准预测[target]的下一步动作。","missing":"[player]凝神聚气,双眼闪烁着深邃的智慧,一招“预测之眼”发动,仿佛未来已经显现在眼前,[target]的一举一动变得难以捉摸。"} | |
{"招式":"智慧编织","description":"[player]手中的线缠绕着[target],编织成一个巧妙的陷阱,一招“智慧编织”完成,如同织女编织云锦,[target]的行动受到了限制。","missing":"[player]手中的线缠绕着,编织成一个巧妙的陷阱,一招“智慧编织”完成,[target]似乎被某种力量限制住了行动。"} | |
{"招式":"虚实交融","description":"[player]身形忽明忽暗,虚实难辨,一招“虚实交融”使出,仿佛有数个[player]一同出招,[target]不知所措。","missing":"[player]身形忽明忽暗,虚实难辨,一招“虚实交融”使出,[target]眼前忽然出现了一片幻影,令其感到迷茫。"} | |
{"招式":"机巧纠缠","description":"[player]运用机巧傀儡术,手中的人工智能傀儡纷纷扑向[target],一招“机巧纠缠”展开,[target]被困在了傀儡的包围中。","missing":"[player]运用机巧傀儡术,手中的人工智能傀儡纷纷扑向,一招“机巧纠缠”展开,[target]四面八方都是傀儡的身影,形势险恶。"} | |
{"招式":"预测之眼","description":"[player]凝神聚气,双眼闪烁着深邃的智慧,一招“预测之眼”发动,仿佛未来已经显现在眼前,[player]能够精准预测[target]的下一步动作。","missing":"[player]凝神聚气,双眼闪烁着深邃的智慧,一招“预测之眼”发动,仿佛未来已经显现在眼前,[target]的一举一动变得难以捉摸。"} | |
{"招式":"智慧编织","description":"[player]手中的线缠绕着[target],编织成一个巧妙的陷阱,一招“智慧编织”完成,如同织女编织云锦,[target]的行动受到了限制。","missing":"[player]手中的线缠绕着,编织成一个巧妙的陷阱,一招“智慧编织”完成,[target]似乎被某种力量限制住了行动。"} | |
{"招式":"虚实交融","description":"[player]身形忽明忽暗,虚实难辨,一招“虚实交融”使出,仿佛有数个[player]一同出招,[target]不知所措。","missing":"[player]身形忽明忽暗,虚实难辨,一招“虚实交融”使出,[target]眼前忽然出现了一片幻影,令其感到迷茫。"} | |
{"招式":"机巧纠缠","description":"[player]运用机巧傀儡术,手中的人工智能傀儡纷纷扑向[target],一招“机巧纠缠”展开,[target]被困在了傀儡的包围中。","missing":"[player]运用机巧傀儡术,手中的人工智能傀儡纷纷扑向,一招“机巧纠缠”展开,[target]四面八方都是傀儡的身影,形势险恶。"} | |
""" | |
skill_lilulu = """ | |
{"招式":"电脑虚像拳","description":"[player]运用电脑幻影拳法,双拳虚虚实实,制造出多个虚像,令[target]难以分辨真伪,招招不离其身。","missing":"[player]运用电脑幻影拳法,双拳虚虚实实,制造出多个虚像,令[target]难以分辨真伪。"} | |
{"招式":"数据掌控击","description":"[player]施展数据掌控击,利用数据流动的速度,犹如电光闪烁,快速攻击[target],剑招迅猛,如电一般。","missing":"[player]施展数据掌控击,利用数据流动的速度,犹如电光闪烁,快速攻击[target],剑招迅猛。"} | |
{"招式":"模拟迷雾拳","description":"[player]施展模拟迷雾拳,释放出模糊幻影,混淆[target]的视线,令其难以辨别攻击方向。","missing":"[player]施展模拟迷雾拳,释放出模糊幻影,混淆[target]的视线,令其难以辨别攻击方向。"} | |
{"招式":"程序化拳法","description":"[player]运用程序化拳法,以不断变化的攻击组合,让[target]难以预测招式,时而快时而慢,攻击变幻莫测。","missing":"[player]运用程序化拳法,以不断变化的攻击组合,让[target]难以预测招式,攻击变幻莫测。"} | |
{"招式":"预测之刃","description":"[player]使出预测之刃,准确地预测[target]的下一步行动,犹如镜中水月,剑锋准确地击中[target]的要害。","missing":"[player]使出预测之刃,准确地预测[target]的下一步行动,犹如镜中水月,剑锋迅速挥出。"} | |
{"招式":"数据漩涡斩","description":"[player]施展数据漩涡斩,利用[target]的动向,将其引导至剑刃之下,然后发动猛烈攻击,犹如漩涡将[target]吸入其中。","missing":"[player]施展数据漩涡斩,利用[target]的动向,将其引导至剑刃之下,然后发动猛烈攻击,剑招异常犀利。"} | |
{"招式":"算法刀舞","description":"[player]挥舞着长剑,使出算法刀舞,刀法如算法一般精准,快速地切入[target]的防线,剑锋连续不断地斩向[target]。","missing":"[player]挥舞着长剑,使出算法刀舞,刀法如算法一般精准,快速地切入[target]的防线,剑招变化多端。"} | |
{"招式":"智能反击剑","description":"[player]运用智能反击剑,反击[target]的招式,使其付出惨痛的代价,一瞬间的犹豫就足以让[target]受伤。","missing":"[player]运用智能反击剑,反击[target]的招式,使其陷入困境,一瞬间的犹豫足以让[target]付出代价。"} | |
""" | |
skill_lengziang = """ | |
{"招式":"冰刃斩破","description":"[player]聚集冰之力量于剑上,使出冰刃斩破,剑锋犹如寒冰之刃斩向[target]的要害部位,寒气逼人,剑光炽烈。","missing":"[player]聚集冰之力量于剑上,使出冰刃斩破,剑锋犹如寒冰之刃,闪电般挥出。"} | |
{"招式":"冰风流转","description":"[player]身体如风一般旋转,同时释放出冰风刃,打击周围的敌人,冷飕飕的寒风环绕,[target]身不由己地被吹向一旁。","missing":"[player]身体如风一般旋转,同时释放出冰风刃,冷飕飕的寒风环绕,风刃在[target]周围飞舞。"} | |
{"招式":"冰龙穿云","description":"[player]召唤出一条冰龙,冰龙穿越云层直扑[target],寒冰之力凝聚在龙爪之上,将[target]冻结一切,一切都难以抵挡。","missing":"[player]召唤出一条冰龙,冰龙穿越云层,龙爪直扑[target],寒冰之力凝聚在龙爪之上,寒意森然。"} | |
{"招式":"冰心彻骨","description":"[player]凝聚冰之力于手,一触即可让[target]骨骼寒冷,动弹不得,彻底冰封,剑锋轻轻触及[target],寒意透骨。","missing":"[player]凝聚冰之力于手,一触即可让[target]骨骼寒冷,动弹不得,寒气透骨,剑锋轻轻触及[target]。"} | |
{"招式":"冰封幻影","description":"[player]运用冰封无影步,快速移动,留下一道冰封的幻影,使[target]无法辨别真伪,幻影与实体交替出现,令[target]不知所措。","missing":"[player]运用冰封无影步,快速移动,留下一道冰封的幻影,使[target]无法辨别真伪。"} | |
{"招式":"无影冻结","description":"[player]以极快的速度接近[target],然后冻结[target]的动作,使其陷入无法自拔的状态,如同身陷冰封之中,动弹不得。","missing":"[player]以极快的速度接近[target],然后冻结[target]的动作,使其陷入无法自拔的状态。"} | |
{"招式":"冰刃闪耀","description":"[player]利用冰之能量,瞬间闪现至[target]身边,发动迅猛的攻击,剑锋如星光般闪烁,将[target]包围其中。","missing":"[player]利用冰之能量,瞬间闪现至[target]身边,发动迅猛的攻击,剑锋如星光般闪烁。"} | |
{"招式":"冰封禅定","description":"[player]静心冥想,将身体冰封,使自己无法被[target]察觉,达到不可捉摸的状态,如同融入冰封禅定之中。","missing":"[player]静心冥想,将身体冰封,使自己无法被[target]察觉,达到不可捉摸的状态。"} | |
""" | |
skill_huangrong = """ | |
{"招式": "狗啸山林", "description": "[player]手中长棒一挥,发出一声凄厉的狗啸,棒影如山林般扑向[target]", "missing": "[player]手中长棒一挥,发出一声凄厉的狗啸,棒影如山林般扑向[target]"} | |
{"招式": "棒影重重", "description": "[player]身形连转,手中长棒化作无数道棒影,重重叠叠地向[target]砸去", "missing": "[player]身形连转,手中长棒化作无数道棒影,重重叠叠地向[target]砸去"} | |
{"招式": "狗尾续貂", "description": "[player]手中长棒如狗尾续貂般灵动,连续变换方向,向[target]连续攻击", "missing": "[player]手中长棒如狗尾续貂般灵动,连续变换方向,向[target]连续攻击"} | |
{"招式": "狗急跳墙", "description": "[player]猛地一跃,手中长棒带起狂风,如狗急跳墙般向[target]砸去", "missing": "[player]猛地一跃,手中长棒带起狂风,如狗急跳墙般向[target]砸去"} | |
{"招式": "落英如雨", "description": "[player]身形一闪,双掌连续拍出,犹如漫天飘落的落英,掌风凌厉,瞬间覆盖[target]周身", "missing": "[player]身形一闪,双掌连续拍出,犹如漫天飘落的落英,掌风凌厉,向[target]周身袭去"} | |
{"招式": "神剑破空", "description": "[player]手中剑光闪烁,一剑刺出,剑气凌厉无比,犹如神剑破开虚空,直指[target]要害", "missing": "[player]手中剑光闪烁,一剑刺出,剑气凌厉无比,直向[target]要害刺去"} | |
{"招式": "飞花流水", "description": "[player]身形如飞,双掌连续拍击,掌风如流水般连绵不绝,将[target]团团围住", "missing": "[player]身形如飞,双掌连续拍击,掌风如流水般连绵不绝,向[target]团团包围"} | |
{"招式": "剑指苍穹", "description": "[player]手中长剑一指,剑光直冲云霄,气势如虹,剑指直指苍穹,威力惊人", "missing": "[player]手中长剑一指,剑光直冲云霄,气势如虹,直指苍穹"} | |
""" | |
def generate_detailed_description(name, description): | |
task_message = f"""我的名字叫“{name}”,假设我是一个武侠中的游戏角色,{description} | |
请基于我的名字和门派,先为我设计2套武功和一套身法或者招架。 | |
再为每套武功设计4个对应招式的名字。""" | |
messages = [ | |
SystemMessage(content=smart_system_prompt), | |
HumanMessage(content=task_message), | |
] | |
chat = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.3) | |
response = chat(messages) | |
return response.content | |
def generate_attr_json(name, description, detailed_description): | |
task_message = f"""我正在为我的武侠游戏设计人物。给定一个人物和这个人物的背景描述(可选) | |
我希望你根据这个人物的背景和名字以及输入的suggestion信息,为我生成这个人的资料,以json的格式输出,包括 | |
description: 这个角色的背景等信息 | |
weapon: 这个人使用的武器种类, | |
major_martial_art: 这个人的主要武学 | |
major_movements: 这个人主要武学的4个对应招式的名字。招式的名字需要尽可能和major_martial_art有关。movements的名字尽量不要使用重复的文字。可以使用形象的动作或者是一些动物的比喻。 | |
major_damage: 主武学的威力,50到150之间的整数。重视速度的功法威力会低一些。重视攻击的功法威力会高一些 | |
secondary_movements: 这个角色的次要武学 | |
secondary_movements: 这个人次要武学的4个对应招式的名字。招式的名字需要尽可能和secondary_movements有关 | |
secondary_damage: 次要武学的威力,50到150之间的整数。 | |
footwork_and_body_method: 这个角色的轻功或者防御功法 | |
STR: 角色的攻击力, 0-20之间的整数 | |
VIT: 角色的体质, 0-20之间的整数 | |
SPD: 角色的速度,0-20之间的整数 | |
""" | |
example_input = """example_input: | |
name: 令狐冲 | |
description: - | |
suggestion:-""" | |
example_output = """example_output: | |
{ | |
"name": "令狐冲", | |
"description": "华山派的大师兄", | |
"weapon": "剑", | |
"major_martial_art": "独孤九剑", | |
"major_movements": ["独孤九剑破剑式","独孤九剑总诀式","独孤九剑破气式","独孤九剑破刀式"], | |
"major_damage": 70, | |
"secondary_martial_art": "华山剑法", | |
"secondary_movements": ["有凤来仪","飞沙走石","百丈飞瀑","青山隐隐"], | |
"secondary_damage":80, | |
"footwork_and_body_method": "华山身法", | |
"STR":10, | |
"VIT":3, | |
"SPD":20 | |
}""" | |
input = f"""input: | |
name: {name}, | |
description: {description}, | |
suggestion: `{detailed_description}` | |
""" | |
messages = [ | |
SystemMessage(content=smart_system_prompt), | |
HumanMessage(content=task_message), | |
HumanMessage(content=example_input), | |
AIMessage(content=example_output), | |
HumanMessage(content=input)] | |
chat = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.1) | |
response = chat(messages) | |
return response.content | |
import json | |
def robust_parsing(input_str): | |
try: | |
return json.loads(input_str) | |
except json.JSONDecodeError: | |
start = input_str.find('{') | |
end = input_str.rfind('}') + 1 | |
if start != -1 and end != -1: | |
try: | |
return json.loads(input_str[start:end]) | |
except json.JSONDecodeError: | |
pass | |
return None | |
def generate_skill_detail(martial_art_name, movements): | |
task_message = """现在我希望你根据武侠小说中的武功和招式的名字。为招式补全合理的description和missing描述。 | |
在description的描述中,player可以击中目标,可以有兵器撞击声响等描述。 | |
在missing描述中,player并不一定击中目标,不可以有击落、击中、夺去这样的描述 | |
在用[player]代表技能的使用者,用[target]代表技能的目标。结果需用合理的jsonl格式展示,下面是一个例子。""" | |
example_input = """example input: | |
martial_art_name = 华山剑法 | |
{"招式":"百丈飞瀑"} | |
{"招式":"百鸟朝凤"} | |
{"招式":"青山隐隐"} | |
{"招式":"飞沙走石"}""" | |
example_output = """example output: | |
{"招式":"百丈飞瀑","description":"[player]翻身回剑,剑诀斜引,一招“百丈飞瀑”,剑锋从半空中直泻下来","missing":"[player]一咬牙,翻身回剑,剑诀斜引,一招“百丈飞瀑”,剑锋从半空中直泻下来"} | |
{"招式":"百鸟朝凤","description":"[player]长剑一起,使一招“百鸟朝凤”,但见剑尖乱颤,霎时间便如化为数十个剑尖,罩住[target]中盘","missing":"[player]使一招“百鸟朝凤”,但见剑尖乱颤,霎时间便如化为数十个剑尖,向[target]飞去"} | |
{"招式":"青山隐隐", "description":"[player]吸一口气,长剑中宫直进,剑尖不住颤动,剑到中途,忽然转而向上,乃是一招“青山隐隐”,端的是若有若无,变幻无方。","missing":"[player]吸一口气,长剑中宫直进,剑尖不住颤动,剑到中途,忽然转而向上,乃是一招“青山隐隐”,端的是若有若无,变幻无方。"} | |
{"招式":"飞沙走石", "description":"长剑挺起,使一招“飞沙走石”,内劲直贯剑尖,寒光点点,挡的一声,击中的[target]胸口。","missing":"长剑挺起,使一招“飞沙走石”,内劲直贯剑尖,寒光点点,直向[target]胸口刺去。"}""" | |
input = "input:\nmartial_art_name = " + martial_art_name + "\n" | |
for m in movements: | |
input += f"""{{"招式":"{m}"\}}\n""" | |
messages = [ | |
SystemMessage(content=smart_system_prompt), | |
HumanMessage(content=task_message), | |
HumanMessage(content=example_input), | |
AIMessage(content=example_output), | |
HumanMessage(content=input)] | |
chat = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.1) | |
response = chat(messages) | |
return response.content | |
def generate_skill_jsonl(json_data): | |
major_skills = generate_skill_detail(json_data["major_martial_art"], json_data["major_movements"]) | |
secondary_skills = generate_skill_detail(json_data["secondary_martial_art"], json_data["secondary_movements"]) | |
all_skills = major_skills + "\n" + secondary_skills | |
ans = "" | |
lines = all_skills.split("\n") | |
for line in lines: | |
json_data = robust_parsing(line) | |
if json_data is not None: | |
json_str = json.dumps(json_data, ensure_ascii=False) | |
ans += json_str + "\n" | |
return ans | |
import random | |
class Player: | |
def __init__(self, attr_json, skill_jsonl): | |
self.name = attr_json["name"] | |
self.STR = attr_json["STR"] | |
self.SPD = attr_json["SPD"] | |
self.VIT = attr_json["VIT"] | |
if "major_damage" in attr_json: | |
self.major_damage = attr_json["major_damage"] | |
else: | |
self.major_damage = 65 | |
if "secondary_damage" in attr_json: | |
self.secondary_damage = attr_json["secondary_damage"] | |
else: | |
self.secondary_damage = 50 | |
self.normalize_attr() | |
if "hp" in attr_json: | |
self.hp = attr_json["hp"] | |
else: | |
self.hp = (self.VIT + 10) * 30 | |
if "skills" in attr_json: | |
self.skills = attr_json["skills"] | |
else: | |
self.skills = self.parsing_skill(skill_jsonl) | |
self.original_json = attr_json | |
def normalize_attr(self): | |
max_attr_value = 20 | |
min_attr_value = 0 | |
regular_attr_sum = 30 | |
xs = [self.STR, self.SPD, self.VIT] | |
current_sum = 0 | |
for i in range(3): | |
xs[i] = min(max_attr_value, xs[i]) | |
xs[i] = max(min_attr_value, xs[i]) | |
current_sum += xs[i] | |
if current_sum > regular_attr_sum: | |
for i in range(3): | |
xs[i] = round(xs[i] * regular_attr_sum / current_sum) | |
elif current_sum < regular_attr_sum: | |
for i in range(3): | |
xs[i] = round(xs[i] * regular_attr_sum / current_sum) | |
random_i = random.randint(0, 2) | |
xs[random_i] = xs[random_i] + regular_attr_sum - sum(xs) | |
self.STR = xs[0] | |
self.SPD = xs[1] | |
self.VIT = xs[2] | |
def get_new_json(self): | |
new_json = self.original_json | |
new_json["STR"] = self.STR | |
new_json["SPD"] = self.SPD | |
new_json["VIT"] = self.VIT | |
new_json["hp"] = self.hp | |
new_json["skills"] = self.skills | |
return new_json | |
def parsing_skill(self, skill_jsonl): | |
skills = [] | |
lines = skill_jsonl.split("\n") | |
for line in lines: | |
if line.strip() == "": | |
continue | |
skill = json.loads(line) | |
skills.append(skill) | |
return skills | |
hit_map = {30: 131, 31: 131, 32: 131, 33: 131, 34: 131, 35: 120, 36: 112, 37: 107, 38: 103, 39: 100, 40: 97, 41: 95, | |
42: 93, 43: 91, 44: 89, 45: 87, 46: 86, 47: 85, 48: 83, 49: 82, 50: 81, 51: 80, 52: 79, 53: 78, 54: 77, | |
55: 76, 56: 75, 57: 74, 58: 73, 59: 72, 60: 71, 61: 70, 62: 69, 63: 69, 64: 68, 65: 67, 66: 66, 67: 65, | |
68: 65, 69: 64, 70: 63, 71: 63, 72: 62, 73: 61, 74: 61, 75: 60, 76: 59, 77: 59, 78: 58, 79: 58, 80: 57, | |
81: 57, 82: 56, 83: 55, 84: 55, 85: 54, 86: 54, 87: 53, 88: 53, 89: 52, 90: 52, 91: 52, 92: 51, 93: 51, | |
94: 50, 95: 50, 96: 49, 97: 49, 98: 49, 99: 48, 100: 48, 101: 47, 102: 47, 103: 47, 104: 46, 105: 46, | |
106: 46, 107: 45, 108: 45, 109: 44, 110: 44, 111: 44, 112: 43, 113: 43, 114: 43, 115: 43, 116: 42, 117: 42, | |
118: 42, 119: 41, 120: 41, 121: 41, 122: 40, 123: 40, 124: 40, 125: 40, 126: 39, 127: 39, 128: 39, 129: 39, | |
130: 38, 131: 38, 132: 38, 133: 38, 134: 37, 135: 37, 136: 37, 137: 37, 138: 36, 139: 36, 140: 36, 141: 36, | |
142: 36, 143: 35, 144: 35, 145: 35, 146: 35, 147: 34, 148: 34, 149: 34, 150: 34, 151: 34, 152: 33, 153: 33, | |
154: 33, 155: 33, 156: 33, 157: 33, 158: 32, 159: 32, 160: 32, 161: 32, 162: 32, 163: 31, 164: 31, 165: 31, | |
166: 31, 167: 31, 168: 31, 169: 30, 170: 30, 171: 30, 172: 30, 173: 30, 174: 30, 175: 30, 176: 29, 177: 29, | |
178: 29, 179: 29, 180: 29} | |
min_damage = 34 | |
max_damage = 180 | |
class GameManager: | |
def __init__(self, attr_json1, attr_json2, skill_jsonl1, skill_jsonl2): | |
self.player1 = Player(attr_json1, skill_jsonl1) | |
self.player2 = Player(attr_json2, skill_jsonl2) | |
def run(self): | |
speed_diff = self.player1.SPD - self.player2.SPD | |
if speed_diff == 0: | |
speed_diff = random.randint(0, 3) * 2 - 3 | |
skill_id_1 = random.randint(0, len(self.player1.skills) - 1) | |
dmg_1 = 60 | |
if skill_id_1 + skill_id_1 < len(self.player1.skills): | |
dmg_1 = self.player1.major_damage | |
else: | |
dmg_1 = self.player1.secondary_damage | |
skill_1_to_2 = self.player1.skills[skill_id_1] | |
skill_1_to_2["dmg"] = dmg_1 | |
skill_id_2 = random.randint(0, len(self.player2.skills) - 1) | |
dmg_2 = 60 | |
if skill_id_2 + skill_id_2 < len(self.player2.skills): | |
dmg_2 = self.player2.major_damage | |
else: | |
dmg_2 = self.player2.secondary_damage | |
skill_2_to_1 = self.player2.skills[skill_id_2] | |
skill_2_to_1["dmg"] = dmg_2 | |
# skill_2_to_1 = random.choice(self.player2.skills) | |
# print(skill_1_to_2) | |
# print(skill_2_to_1) | |
damage_1_to_2 = self.compute_damage(skill_1_to_2, self.player1, self.player2) | |
damage_2_to_1 = self.compute_damage(skill_2_to_1, self.player2, self.player1) | |
ratio_1_to_2 = self.compute_ratio(skill_1_to_2, self.player1, self.player2) | |
ratio_2_to_1 = self.compute_ratio(skill_2_to_1, self.player2, self.player1) | |
if_hitted_1_to_2 = random.random() * 100.0 < ratio_1_to_2 | |
if_hitted_2_to_1 = random.random() * 100.0 < ratio_2_to_1 | |
desc_1_to_2 = skill_1_to_2["description"] if if_hitted_1_to_2 else skill_1_to_2["missing"] | |
desc_1_to_2 = desc_1_to_2.replace("[player]", self.player1.name).replace("[target]", self.player2.name) | |
if if_hitted_1_to_2: | |
desc_1_to_2 += f"造成了{round(damage_1_to_2)}的伤害。" | |
else: | |
desc_1_to_2 += f"{self.player2.name}躲了过去" | |
desc_2_to_1 = skill_2_to_1["description"] if if_hitted_2_to_1 else skill_2_to_1["missing"] | |
desc_2_to_1 = desc_2_to_1.replace("[player]", self.player2.name).replace("[target]", self.player1.name) | |
if if_hitted_2_to_1: | |
desc_2_to_1 += f"造成了{round(damage_2_to_1)}的伤害。" | |
else: | |
desc_2_to_1 += f"{self.player1.name}躲了过去" | |
self.flag = "continue" | |
ans_msg = [] | |
if self.player2.hp <= 0: | |
ans_msg.append((None, f"{self.player2.name}战败了")) | |
return ans_msg, self.player1.get_new_json(), self.player2.get_new_json() | |
if self.player1.hp <= 0: | |
ans_msg.append((f"{self.player1.name}战败了", None)) | |
return ans_msg, self.player1.get_new_json(), self.player2.get_new_json() | |
if speed_diff > 0: | |
if if_hitted_1_to_2: | |
self.player2.hp -= damage_1_to_2 | |
ans_msg.append((desc_1_to_2, None)) | |
if self.player2.hp > 0 and if_hitted_2_to_1: | |
self.player1.hp -= damage_2_to_1 | |
if self.player2.hp > 0: | |
ans_msg.append((None, desc_2_to_1)) | |
else: | |
if if_hitted_2_to_1: | |
self.player1.hp -= damage_2_to_1 | |
ans_msg.append((None, desc_2_to_1)) | |
if self.player1.hp > 0 and if_hitted_1_to_2: | |
self.player2.hp -= damage_1_to_2 | |
if self.player1.hp > 0: | |
ans_msg.append((desc_1_to_2, None)) | |
if self.player2.hp <= 0: | |
ans_msg.append((None, f"{self.player2.name}战败了")) | |
self.flag = "player1_win" | |
if self.player1.hp <= 0: | |
ans_msg.append((f"{self.player1.name}战败了", None)) | |
self.flag = "player2_win" | |
return ans_msg, self.player1.get_new_json(), self.player2.get_new_json() | |
# default "damage": 60, "hit": 76, | |
def compute_damage(self, skill, player, target): | |
damage_ratio = skill["dmg"] | |
damage_ratio = max(min_damage, damage_ratio) | |
damage_ratio = min(max_damage, min_damage) | |
skill_damage = damage_ratio + player.STR * 2 | |
attack_diff = player.STR - target.VIT * 0.5 | |
damage = skill_damage * (1 + 0.95 * (-0.5 + 1.0 / (1.0 + exp(-attack_diff / 6.0)))) | |
return damage | |
def compute_ratio(self, skill, player, target): | |
damage_ratio = skill["dmg"] | |
damage_ratio = round(damage_ratio) | |
damage_ratio = max(min_damage, damage_ratio) | |
damage_ratio = min(max_damage, min_damage) | |
skill_hit = hit_map[damage_ratio] | |
speed_diff = player.SPD - target.SPD | |
hit_factor = (-0.5 + 1.0 / (1.0 + exp(-speed_diff / 6.0))) / 2.0 * max(30.0, skill_hit) | |
return hit_factor + skill_hit | |
fighter_save_name = 'LuotuoFighter.txt' | |
import os | |
datas = [] | |
if not os.path.exists(fighter_save_name): | |
data = { | |
"name": "李鲁鲁", | |
"attr_str": attr_lilulu, | |
"skills": skill_lilulu | |
} | |
datas.append(data) | |
data = { | |
"name": "冷子昂", | |
"attr_str": attr_lengziang, | |
"skills": skill_lengziang | |
} | |
datas.append(data) | |
data = { | |
"name": "黄蓉", | |
"attr_str": json_str_huangrong, | |
"skills": skill_huangrong | |
} | |
datas.append(data) | |
with open(fighter_save_name, 'w', encoding="utf-8") as f: | |
for data in datas: | |
f.write(json.dumps(data, ensure_ascii=False) + "\n") | |
else: | |
with open(fighter_save_name, 'r', encoding="utf-8") as f: | |
for line in f.readlines(): | |
data = json.loads(line) | |
datas.append(data) | |
fighter_data = datas | |
elo_table = {} | |
def get_unique_from_data(data): | |
name = data["name"] | |
json_data = json.loads(data["attr_str"]) | |
return get_unique_from_json(name, json_data) | |
def get_unique_from_json(name, json_data): | |
if "STR" in json_data and "VIT" in json_data and "SPD" in json_data and "major_damage" in json_data and "secondary_damage" in json_data: | |
return name + "_" + str(json_data["major_damage"]) + "_" + str(json_data["secondary_damage"]) | |
else: | |
return None | |
unique_to_score = {} | |
name_to_top_unique = {} | |
from math import exp | |
elo_start = 1200 | |
elo_K = 15 | |
for data in fighter_data: | |
unique_name = get_unique_from_data(data) | |
score = elo_start | |
unique_to_score[unique_name] = elo_start | |
name = data["name"] | |
name_to_top_unique[name] = unique_name | |
def get_rank_str(top_n=20): | |
global unique_to_score | |
global name_to_top_unique | |
score_name_pair = [] | |
for name in name_to_top_unique: | |
unique = name_to_top_unique[name] | |
score = unique_to_score[unique] | |
score_name_pair.append((unique, score)) | |
top_n = min(top_n, len(name_to_top_unique)) | |
score_name_pair.sort(key=lambda x: x[1], reverse=True) | |
ans = "" | |
for i in range(top_n): | |
name = score_name_pair[i][0].split("_")[0] | |
ans += f"第{i + 1}名" + name + " - " + str(round(score_name_pair[i][1])) + " | " | |
return ans | |
def update_elo(winner_unique, loser_unique): | |
print(winner_unique + " wins " + loser_unique) | |
update_winner = True | |
update_loser = True | |
global unique_to_score | |
global name_to_top_unique | |
if winner_unique not in unique_to_score: | |
unique_to_score[winner_unique] = elo_start | |
update_loser = False | |
if loser_unique not in unique_to_score: | |
unique_to_score[loser_unique] = elo_start | |
Ra = unique_to_score[winner_unique] | |
Rb = unique_to_score[loser_unique] | |
Ea = 1 / (1 + exp((Rb - Ra) / 400)) | |
Eb = 1 / (1 + exp((Ra - Rb) / 400)) | |
Sa = 1 | |
Sb = 0 | |
unique_to_score[winner_unique] = Ra + elo_K * (Sa - Ea) | |
unique_to_score[loser_unique] = Rb + elo_K * (Sb - Eb) | |
winner_name = winner_unique.split("_")[0] | |
if winner_name not in name_to_top_unique: | |
name_to_top_unique[winner_name] = winner_unique | |
winner_unique_on_rank = name_to_top_unique[winner_name] | |
if unique_to_score[winner_unique] > unique_to_score[winner_unique_on_rank]: | |
name_to_top_unique[winner_name] = winner_unique | |
def get_random_fighter(): | |
global fighter_data | |
return random.choice(fighter_data) | |
def searching_fighter(name): | |
global fighter_data | |
for data in fighter_data: | |
if data["name"] == name: | |
return data | |
return get_random_fighter() | |
def add_fighter(attr_json_str, skill_jsonl): | |
attr_json = json.loads(attr_json_str) | |
if "skills" in attr_json: | |
del attr_json["skills"] | |
if "hp" in attr_json: | |
del attr_json["hp"] | |
new_attr_json_str = json.dumps(attr_json, ensure_ascii=False) | |
global fighter_data | |
name = attr_json["name"] | |
data = { | |
"name": name, | |
"attr_str": new_attr_json_str, | |
"skills": skill_jsonl | |
} | |
fighter_data.append(data) | |
print(json.dumps(data, ensure_ascii=False)) | |
with open(fighter_save_name, 'a', encoding="utf-8") as f: | |
f.write(json.dumps(data, ensure_ascii=False) + "\n") | |
def searching_fighter_on_elo(name): | |
global fighter_data | |
global name_to_top_unique | |
if name in name_to_top_unique: | |
top_unique = name_to_top_unique[name] | |
for data in fighter_data: | |
if get_unique_from_data(data) == top_unique: | |
return data | |
return searching_fighter(name) | |
else: | |
return searching_fighter(name) | |
import gradio as gr | |
def get_attr_str_short(attr_json): | |
if "hp" in attr_json: | |
ans = "血量:" + str(attr_json["hp"]) + "\n" | |
else: | |
ans = "" | |
ans += "力量:" + str(attr_json["STR"]) + "\n体质:" + str(attr_json["VIT"]) + "\n速度:" + str(attr_json["SPD"]) | |
return ans | |
def get_skill_str_short(attr_json): | |
ans = attr_json["major_martial_art"] + ":\n" | |
for skill in attr_json["major_movements"]: | |
ans += skill + "-" | |
ans += "\n" + attr_json["secondary_martial_art"] + ":\n" | |
for skill in attr_json["secondary_movements"]: | |
ans += skill + "-" | |
ans += "\n 防御:" + attr_json["footwork_and_body_method"] | |
return ans | |
def generate_ma(name, desc, display_board): | |
if name.strip() == "": | |
return "", "", "", "", display_board, "角色名不能为空,输入角色名后生成功法或者创建角色" | |
status = "请为角色进一步生成详细功法" | |
role_detail = ma_foo_answer | |
if name == "李鲁鲁": | |
json_answer = json.loads(attr_lilulu) | |
elif name == "冷子昂": | |
json_answer = json.loads(attr_lengziang) | |
else: | |
role_detail = generate_detailed_description(name, desc) | |
json_str = generate_attr_json(name, desc, role_detail) | |
json_answer = robust_parsing(json_str) | |
json_answer_str = json.dumps(json_answer, ensure_ascii=False) | |
skill_desc = get_skill_str_short(json_answer) | |
display_board.append(("生成人物" + json_answer["name"] + "\n" + skill_desc, None)) | |
return role_detail, json_answer_str, get_attr_str_short(json_answer), skill_desc, display_board, status | |
def generate_madetail(player_attribute, display_board): | |
if player_attribute.strip() == "": | |
return "", display_board, "需要生成武功名称,再生成描述。或者直接随机召唤角色" | |
status = "确认两个角色都生成之后,可以进入战斗" | |
json_answer = json.loads(player_attribute) | |
generate_flag = False | |
ans = skill_foo_desc | |
if json_answer["name"] == "李鲁鲁": | |
ans = skill_lilulu | |
elif json_answer["name"] == "冷子昂": | |
ans = skill_lengziang | |
else: | |
ans = generate_skill_jsonl(json_answer) | |
generate_flag = True | |
display_board.append(("为" + json_answer["name"] + "生成详细的功法描述\n", None)) | |
if generate_flag: | |
player = Player(json_answer, ans) | |
unique_name = get_unique_from_json(json_answer["name"], json_answer) | |
if len(player.skills) > 1 and unique_name != None: | |
add_fighter(player_attribute, ans) | |
display_board.append(("将新角色" + json_answer["name"] + "录入到数据库\n", None)) | |
return ans, display_board, status | |
def continue_fight(detailed_attr_player1, detailed_attr_player2, detailed_skill_player1, detailed_skill_player2, | |
display_board): | |
if detailed_attr_player1.strip() == "" or detailed_attr_player2.strip() == "" or detailed_skill_player1.strip() == "" or detailed_skill_player2.strip() == "": | |
str1 = "" | |
if detailed_attr_player1.strip() != "": | |
str1 = get_attr_str_short(json.loads(detailed_attr_player1)) | |
str2 = "" | |
if detailed_attr_player2.strip() != "": | |
str2 = get_attr_str_short(json.loads(detailed_attr_player2)) | |
return detailed_attr_player1, detailed_attr_player2, display_board, str1, str2, "请重新检查人物的生成情况" | |
json1 = json.loads(detailed_attr_player1) | |
json2 = json.loads(detailed_attr_player2) | |
name1 = json1["name"] | |
name2 = json2["name"] | |
status = f"""{name1} 大战 {name2}""" | |
if "hp" in json1 and "hp" in json2: | |
if json1["hp"] <= 0 and json1["hp"] < json2["hp"]: | |
unique1 = get_unique_from_json(name1, json1) | |
unique2 = get_unique_from_json(name2, json2) | |
return detailed_attr_player1, detailed_attr_player2, display_board, "", "", "战斗结束!请清除战斗或者重新生成人物" | |
if json2["hp"] <= 0 and json2["hp"] < json1["hp"]: | |
unique1 = get_unique_from_json(name1, json1) | |
unique2 = get_unique_from_json(name2, json2) | |
return detailed_attr_player1, detailed_attr_player2, display_board, "", "", "战斗结束!请清除战斗或者重新生成人物" | |
game_manager = GameManager(json1, json2, detailed_skill_player1, detailed_skill_player2) | |
msgs, new_player1, new_player2 = game_manager.run() | |
for msg in msgs: | |
display_board.append(msg) | |
if game_manager.flag == "player1_win": | |
unique1 = get_unique_from_json(name1, json1) | |
unique2 = get_unique_from_json(name2, json2) | |
update_elo(unique1, unique2) | |
elif game_manager.flag == "player2_win": | |
unique1 = get_unique_from_json(name1, json1) | |
unique2 = get_unique_from_json(name2, json2) | |
update_elo(unique2, unique1) | |
new_player1_str = json.dumps(new_player1, ensure_ascii=False) | |
new_player2_str = json.dumps(new_player2, ensure_ascii=False) | |
return new_player1_str, new_player2_str, display_board, get_attr_str_short(new_player1), get_attr_str_short( | |
new_player2), status | |
def callback_random_role(display_board): | |
data = get_random_fighter() | |
name = data["name"] | |
json_str = data["attr_str"] | |
skills_jsonl = data["skills"] | |
json_answer = json.loads(json_str) | |
display_board.append(("从数据库选择角色" + name, None)) | |
return name, json_str, skills_jsonl, get_attr_str_short(json_answer), get_skill_str_short( | |
json_answer), display_board | |
def callback_role_from_name(display_board, name): | |
data = searching_fighter_on_elo(name) | |
json_str = data["attr_str"] | |
skills_jsonl = data["skills"] | |
json_answer = json.loads(json_str) | |
display_board.append(("从数据库选择角色" + name, None)) | |
return name, json_str, skills_jsonl, get_attr_str_short(json_answer), get_skill_str_short( | |
json_answer), display_board | |
def callback_clean_fight(display_board, detailed_attr_player1, detailed_attr_player2): | |
display_board = [] | |
json1 = json.loads(detailed_attr_player1) | |
json2 = json.loads(detailed_attr_player2) | |
if "hp" in json1: | |
del json1["hp"] | |
if "hp" in json2: | |
del json2["hp"] | |
new_detailed_attr_player1 = json.dumps(json1, ensure_ascii=False) | |
new_detailed_attr_player2 = json.dumps(json2, ensure_ascii=False) | |
return display_board, new_detailed_attr_player1, new_detailed_attr_player2 | |
def callback_refresh_rank(): | |
return get_rank_str() | |
with gr.Blocks() as demo: | |
gr.Markdown( | |
""" | |
# LuotuoFighter | |
## 骆驼大乱斗 | |
Implemented by 李鲁鲁 | |
项目链接 https://github.com/LC1332/Chat-Haruhi-Suzumiya | |
这个原型是为了百川Hackathon而初步建立 | |
三种角色建立方式: 根据姓名召唤、随机召唤,或者输入姓名和描述后生成新的人物 | |
姓名召唤会召唤同名人物中战力最强的那个 | |
由于Gradio特性,建立角色要花个几十秒,先点击生成功法,再点击生成功法描述 | |
""" | |
) | |
with gr.Row(): | |
with gr.Column(): | |
current_status = gr.Textbox(label="当前状态", value="请输入角色1的姓名之后生成功法,或进行召唤") | |
display_board = gr.Chatbot(height=800) | |
submit_fight2 = gr.Button("继续战斗!") | |
clean_fight = gr.Button("清空战斗") | |
with gr.Column(): | |
with gr.Row(): | |
name_player1 = gr.Textbox(label="角色1姓名", scale=1, interactive=True, value="李鲁鲁") | |
desc_player1 = gr.Textbox(label="角色1描述", scale=20, value="师从,主修剑法", interactive=True) | |
with gr.Row(): | |
generate_ma_player1 = gr.Button("生成功法") | |
generate_madetail_player1 = gr.Button("生成功法描述") | |
with gr.Row(): | |
random_sel_player1 = gr.Button("随机召唤") | |
call_player1_with_name1 = gr.Button("根据姓名召唤") | |
with gr.Row(): | |
name_player2 = gr.Textbox(label="角色2姓名", scale=1, interactive=True, value="冷子昂") | |
desc_player2 = gr.Textbox(label="角色2描述", scale=20, value="师从,主修剑法", interactive=True) | |
with gr.Row(): | |
generate_ma_player2 = gr.Button("生成功法") | |
generate_madetail_player2 = gr.Button("生成功法描述") | |
with gr.Row(): | |
random_sel_player2 = gr.Button("随机召唤2") | |
call_player1_with_name2 = gr.Button("根据姓名召唤2") | |
with gr.Row(): | |
submit_fight = gr.Button("继续战斗!") | |
with gr.Row(): | |
attr_player1 = gr.TextArea(label="角色1属性") | |
skill_player1 = gr.TextArea(label="角色1功法描述") | |
with gr.Row(): | |
attr_player2 = gr.TextArea(label="角色2属性") | |
skill_player2 = gr.TextArea(label="角色2功法描述") | |
with gr.Row(): | |
refresh_rank = gr.Button("刷新天梯") | |
with gr.Row(): | |
rank_showboard = gr.TextArea(label="天梯") | |
with gr.Row(): | |
with gr.Column(): | |
detailed_description_player1 = gr.TextArea(label="角色1具体描述") | |
detailed_skill_player1 = gr.TextArea(label="角色1技能json") | |
detailed_attr_player1 = gr.TextArea(label="角色1属性json") | |
with gr.Column(): | |
detailed_description_player2 = gr.TextArea(label="角色2具体描述") | |
detailed_skill_player2 = gr.TextArea(label="角色2技能json") | |
detailed_attr_player2 = gr.TextArea(label="角色2属性json") | |
generate_ma_player1.click(fn=generate_ma, inputs=[name_player1, desc_player1, display_board], | |
outputs=[detailed_description_player1, detailed_attr_player1, attr_player1, skill_player1, | |
display_board, current_status]) | |
generate_ma_player2.click(fn=generate_ma, inputs=[name_player2, desc_player2, display_board], | |
outputs=[detailed_description_player2, detailed_attr_player2, attr_player2, skill_player2, | |
display_board, current_status]) | |
generate_madetail_player1.click(fn=generate_madetail, inputs=[detailed_attr_player1, display_board], | |
outputs=[detailed_skill_player1, display_board, current_status]) | |
generate_madetail_player2.click(fn=generate_madetail, inputs=[detailed_attr_player2, display_board], | |
outputs=[detailed_skill_player2, display_board, current_status]) | |
random_sel_player1.click(fn=callback_random_role, inputs=[display_board], | |
outputs=[name_player1, detailed_attr_player1, detailed_skill_player1, attr_player1, | |
skill_player1, display_board]) | |
random_sel_player2.click(fn=callback_random_role, inputs=[display_board], | |
outputs=[name_player2, detailed_attr_player2, detailed_skill_player2, attr_player2, | |
skill_player2, display_board]) | |
call_player1_with_name1.click(fn=callback_role_from_name, inputs=[display_board, name_player1], | |
outputs=[name_player1, detailed_attr_player1, detailed_skill_player1, attr_player1, | |
skill_player1, display_board]) | |
call_player1_with_name2.click(fn=callback_role_from_name, inputs=[display_board, name_player2], | |
outputs=[name_player2, detailed_attr_player2, detailed_skill_player2, attr_player2, | |
skill_player2, display_board]) | |
refresh_rank.click(fn=callback_refresh_rank, inputs=[], outputs=[rank_showboard]) | |
clean_fight.click(fn=callback_clean_fight, inputs=[display_board, detailed_attr_player1, detailed_attr_player2], | |
outputs=[display_board, detailed_attr_player1, detailed_attr_player2]) | |
submit_fight.click(fn=continue_fight, | |
inputs=[detailed_attr_player1, detailed_attr_player2, detailed_skill_player1, | |
detailed_skill_player2, display_board], | |
outputs=[detailed_attr_player1, detailed_attr_player2, display_board, attr_player1, attr_player2, | |
current_status]) | |
submit_fight2.click(fn=continue_fight, | |
inputs=[detailed_attr_player1, detailed_attr_player2, detailed_skill_player1, | |
detailed_skill_player2, display_board], | |
outputs=[detailed_attr_player1, detailed_attr_player2, display_board, attr_player1, | |
attr_player2, current_status]) | |
demo.launch(debug=False, share=True) | |