Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# ===== app.py - Single Gradio App with SQLite =====
|
| 2 |
"""
|
| 3 |
Life Frontier: Partner's Concerto
|
| 4 |
Single-app turn-based game with SQLite state management
|
|
@@ -551,20 +551,12 @@ def execute_rest(player: Dict, rest_type: str) -> Tuple[bool, str, Dict]:
|
|
| 551 |
|
| 552 |
def execute_solo_task(player: Dict, task: Dict) -> Tuple[bool, str, Dict]:
|
| 553 |
"""Execute task solo"""
|
| 554 |
-
# Check
|
| 555 |
-
|
| 556 |
-
if
|
| 557 |
-
return False,
|
| 558 |
-
|
| 559 |
-
for res, amount in solo_req.items():
|
| 560 |
-
if res == "HP" and player['hp'] < amount:
|
| 561 |
-
return False, f"Not enough HP (need {amount})", {}
|
| 562 |
-
if res == "CP" and player['cp'] < amount:
|
| 563 |
-
return False, f"Not enough CP (need {amount})", {}
|
| 564 |
|
| 565 |
-
|
| 566 |
-
if 'cost' in task and player['money'] < task['cost']:
|
| 567 |
-
return False, f"Not enough money (need ${task['cost']})", {}
|
| 568 |
|
| 569 |
# Execute
|
| 570 |
updates = {
|
|
@@ -596,8 +588,12 @@ def execute_solo_task(player: Dict, task: Dict) -> Tuple[bool, str, Dict]:
|
|
| 596 |
updates['stress'] = max(0, updates.get('stress', player['stress']) + value)
|
| 597 |
elif eff_type == 'hp':
|
| 598 |
updates['hp'] = min(updates.get('hp', player['hp']) + value, player['hp_max'])
|
|
|
|
|
|
|
| 599 |
elif eff_type == 'hp_max':
|
| 600 |
updates['hp_max'] = player['hp_max'] + value
|
|
|
|
|
|
|
| 601 |
elif effect == 'recover_all':
|
| 602 |
updates['hp'] = player['hp_max']
|
| 603 |
updates['cp'] = player['cp_max']
|
|
@@ -605,15 +601,77 @@ def execute_solo_task(player: Dict, task: Dict) -> Tuple[bool, str, Dict]:
|
|
| 605 |
# Check elimination
|
| 606 |
if updates.get('stress', player['stress']) >= STRESS_THRESHOLD:
|
| 607 |
updates['is_eliminated'] = 1
|
| 608 |
-
return True, f"β οΈ Task completed but ELIMINATED (stress {updates['stress']})", updates
|
| 609 |
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 615 |
|
| 616 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
|
| 618 |
# ===== GRADIO UI =====
|
| 619 |
def create_ui():
|
|
@@ -656,7 +714,17 @@ def create_ui():
|
|
| 656 |
# Player 1 Panel
|
| 657 |
with gr.Column():
|
| 658 |
gr.Markdown("### Player 1")
|
| 659 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 660 |
|
| 661 |
# Center - Actions
|
| 662 |
with gr.Column():
|
|
@@ -665,32 +733,64 @@ def create_ui():
|
|
| 665 |
|
| 666 |
gr.Markdown("**Rest Actions**")
|
| 667 |
with gr.Row():
|
| 668 |
-
quick_nap_btn = gr.Button("Quick Nap (3h)")
|
| 669 |
-
full_sleep_btn = gr.Button("Full Sleep (9h)")
|
| 670 |
-
deep_rest_btn = gr.Button("Deep Rest (12h)")
|
| 671 |
|
| 672 |
gr.Markdown("**Available Tasks**")
|
| 673 |
tasks_display = gr.Dataframe(
|
| 674 |
-
headers=["ID", "Name", "Type", "Time", "
|
| 675 |
-
label="Public Tasks"
|
|
|
|
| 676 |
)
|
| 677 |
|
| 678 |
-
task_select = gr.Dropdown(label="Select Task")
|
| 679 |
-
solo_task_btn = gr.Button("Execute Solo Task", variant="primary")
|
| 680 |
|
| 681 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 682 |
|
| 683 |
action_result = gr.Markdown()
|
| 684 |
|
| 685 |
# Player 2 Panel
|
| 686 |
with gr.Column():
|
| 687 |
gr.Markdown("### Player 2")
|
| 688 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
|
| 690 |
gr.Markdown("### Spectators")
|
| 691 |
spectators_display = gr.Markdown("*No spectators*")
|
| 692 |
|
| 693 |
-
refresh_btn = gr.Button("π Refresh Game State")
|
| 694 |
|
| 695 |
with gr.Tab("π Rankings"):
|
| 696 |
rankings_table = gr.Dataframe(
|
|
@@ -735,139 +835,97 @@ def create_ui():
|
|
| 735 |
|
| 736 |
def refresh_game_state(session):
|
| 737 |
if not session.get('room_id'):
|
| 738 |
-
return ("*No game joined*",
|
| 739 |
-
"", "", 0, 0, 0, 0, 0, 0, [], "", "*No spectators*", "")
|
| 740 |
|
| 741 |
state = db.get_room_state(session['room_id'])
|
| 742 |
if not state:
|
| 743 |
-
return ("*Room not found*",
|
| 744 |
-
"", "", 0, 0, 0, 0, 0, 0, [], "", "*No spectators*", "")
|
| 745 |
|
| 746 |
room = state['room']
|
| 747 |
players = state['players']
|
| 748 |
|
| 749 |
# Status
|
| 750 |
status_md = f"""
|
| 751 |
-
**Room:** {session['room_id']} | **Round:** {room['current_round']}/{room['total_rounds']}
|
| 752 |
**Status:** {room['status'].upper()} | **Current Turn:** Player {room['current_turn'] + 1}
|
| 753 |
"""
|
| 754 |
|
| 755 |
if room['status'] == 'finished':
|
| 756 |
-
|
| 757 |
-
status_md += f"\n\nπ **GAME OVER!** Winner: **{winner_name}**"
|
| 758 |
|
| 759 |
-
#
|
| 760 |
p1_data = players[0] if len(players) > 0 else {}
|
| 761 |
-
p1_vals = (
|
| 762 |
-
p1_data.get('name', ''),
|
| 763 |
-
p1_data.get('character_id', ''),
|
| 764 |
-
p1_data.get('time', 0),
|
| 765 |
-
p1_data.get('money', 0),
|
| 766 |
-
p1_data.get('hp', 0),
|
| 767 |
-
p1_data.get('cp', 0),
|
| 768 |
-
p1_data.get('stress', 0),
|
| 769 |
-
p1_data.get('qol', 0)
|
| 770 |
-
)
|
| 771 |
-
|
| 772 |
-
# Player 2 data
|
| 773 |
p2_data = players[1] if len(players) > 1 else {}
|
| 774 |
-
p2_vals = (
|
| 775 |
-
p2_data.get('name', ''),
|
| 776 |
-
p2_data.get('character_id', ''),
|
| 777 |
-
p2_data.get('time', 0),
|
| 778 |
-
p2_data.get('money', 0),
|
| 779 |
-
p2_data.get('hp', 0),
|
| 780 |
-
p2_data.get('cp', 0),
|
| 781 |
-
p2_data.get('stress', 0),
|
| 782 |
-
p2_data.get('qol', 0)
|
| 783 |
-
)
|
| 784 |
|
| 785 |
-
# Tasks
|
| 786 |
task_rows = []
|
| 787 |
-
task_options = []
|
| 788 |
for task_id in state['task_ids']:
|
| 789 |
task = next((t for t in TASKS if t['id'] == task_id), None)
|
| 790 |
if task:
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
if 'reward' in task and task['reward'] > 0:
|
| 795 |
-
reward_str = f"${task['reward']}"
|
| 796 |
-
elif 'qol' in task:
|
| 797 |
-
reward_str = f"{task['qol']} QoL"
|
| 798 |
-
else:
|
| 799 |
-
reward_str = "Utility"
|
| 800 |
-
|
| 801 |
task_rows.append([
|
| 802 |
task['id'], task['name'], task['type'],
|
| 803 |
-
f"{task['time']}h",
|
| 804 |
])
|
| 805 |
-
|
|
|
|
|
|
|
| 806 |
|
| 807 |
# Spectators
|
| 808 |
spec_md = f"π₯ {len(state['spectators'])} watching: {', '.join(state['spectators'])}" if state['spectators'] else "*No spectators*"
|
| 809 |
|
| 810 |
# Turn indicator
|
| 811 |
if session.get('is_spectator'):
|
| 812 |
-
turn_msg = "ποΈ
|
| 813 |
-
elif room['status'] == 'waiting':
|
| 814 |
-
turn_msg = "β³ **Waiting for Player 2 to join...**"
|
| 815 |
-
elif room['status'] == 'finished':
|
| 816 |
-
turn_msg = "π **Game Finished!** Check Rankings tab."
|
| 817 |
elif session.get('player_slot') == room['current_turn']:
|
| 818 |
turn_msg = "β
**YOUR TURN!** Take an action below."
|
| 819 |
else:
|
| 820 |
-
turn_msg = "β³
|
| 821 |
|
| 822 |
-
return (status_md,
|
| 823 |
|
| 824 |
def execute_rest_handler(session, rest_type):
|
| 825 |
if not session.get('room_id') or session.get('is_spectator'):
|
| 826 |
-
return "β Not in game as player"
|
| 827 |
|
| 828 |
state = db.get_room_state(session['room_id'])
|
| 829 |
if not state:
|
| 830 |
-
return "β Room not found"
|
| 831 |
|
| 832 |
room = state['room']
|
| 833 |
-
if room['status'] != 'playing':
|
| 834 |
-
return "β Game not in progress"
|
| 835 |
-
|
| 836 |
if room['current_turn'] != session['player_slot']:
|
| 837 |
-
return "β Not your turn!"
|
| 838 |
|
| 839 |
player = state['players'][session['player_slot']]
|
| 840 |
success, msg, updates = execute_rest(player, rest_type)
|
| 841 |
|
| 842 |
if success:
|
| 843 |
db.update_player(session['room_id'], session['player_slot'], updates)
|
| 844 |
-
return f"β
{msg}"
|
| 845 |
|
| 846 |
-
return f"β {msg}"
|
| 847 |
|
| 848 |
def execute_solo_task_handler(session, task_id):
|
| 849 |
if not session.get('room_id') or session.get('is_spectator'):
|
| 850 |
-
return "β Not in game as player"
|
| 851 |
|
| 852 |
if not task_id:
|
| 853 |
-
return "β Select a task first"
|
| 854 |
|
| 855 |
state = db.get_room_state(session['room_id'])
|
| 856 |
if not state:
|
| 857 |
-
return "β Room not found"
|
| 858 |
|
| 859 |
room = state['room']
|
| 860 |
-
if room['status'] != 'playing':
|
| 861 |
-
return "β Game not in progress"
|
| 862 |
-
|
| 863 |
if room['current_turn'] != session['player_slot']:
|
| 864 |
-
return "β Not your turn!"
|
| 865 |
|
| 866 |
player = state['players'][session['player_slot']]
|
| 867 |
task = next((t for t in TASKS if t['id'] == task_id), None)
|
| 868 |
|
| 869 |
if not task:
|
| 870 |
-
return "β Task not found"
|
| 871 |
|
| 872 |
success, msg, updates = execute_solo_task(player, task)
|
| 873 |
|
|
@@ -878,27 +936,24 @@ def create_ui():
|
|
| 878 |
updates['completed_tasks'] = json.dumps(completed)
|
| 879 |
|
| 880 |
db.update_player(session['room_id'], session['player_slot'], updates)
|
| 881 |
-
return f"β
{msg}"
|
| 882 |
|
| 883 |
-
return f"β {msg}"
|
| 884 |
|
| 885 |
def end_turn_handler(session):
|
| 886 |
if not session.get('room_id') or session.get('is_spectator'):
|
| 887 |
-
return "β Not in game as player"
|
| 888 |
|
| 889 |
state = db.get_room_state(session['room_id'])
|
| 890 |
if not state:
|
| 891 |
-
return "β Room not found"
|
| 892 |
|
| 893 |
room = state['room']
|
| 894 |
-
if room['status'] != 'playing':
|
| 895 |
-
return "β Game not in progress"
|
| 896 |
-
|
| 897 |
if room['current_turn'] != session['player_slot']:
|
| 898 |
-
return "β Not your turn!"
|
| 899 |
|
| 900 |
db.end_turn(session['room_id'], session['player_slot'])
|
| 901 |
-
return "β
Turn ended. Waiting for partner..."
|
| 902 |
|
| 903 |
def load_rankings():
|
| 904 |
rankings = db.get_rankings(20)
|
|
@@ -930,75 +985,63 @@ def create_ui():
|
|
| 930 |
refresh_btn.click(
|
| 931 |
refresh_game_state,
|
| 932 |
inputs=[session_state],
|
| 933 |
-
outputs=[status_display,
|
| 934 |
-
|
| 935 |
-
p2_name, p2_character, p2_time, p2_money, p2_hp, p2_cp, p2_stress, p2_qol,
|
| 936 |
-
tasks_display, task_select, spectators_display, your_turn_msg]
|
| 937 |
)
|
| 938 |
|
| 939 |
quick_nap_btn.click(
|
| 940 |
lambda s: execute_rest_handler(s, "quick_nap"),
|
| 941 |
inputs=[session_state],
|
| 942 |
-
outputs=[action_result]
|
| 943 |
).then(
|
| 944 |
refresh_game_state,
|
| 945 |
inputs=[session_state],
|
| 946 |
-
outputs=[status_display,
|
| 947 |
-
|
| 948 |
-
p2_name, p2_character, p2_time, p2_money, p2_hp, p2_cp, p2_stress, p2_qol,
|
| 949 |
-
tasks_display, task_select, spectators_display, your_turn_msg]
|
| 950 |
)
|
| 951 |
|
| 952 |
full_sleep_btn.click(
|
| 953 |
lambda s: execute_rest_handler(s, "full_sleep"),
|
| 954 |
inputs=[session_state],
|
| 955 |
-
outputs=[action_result]
|
| 956 |
).then(
|
| 957 |
refresh_game_state,
|
| 958 |
inputs=[session_state],
|
| 959 |
-
outputs=[status_display,
|
| 960 |
-
|
| 961 |
-
p2_name, p2_character, p2_time, p2_money, p2_hp, p2_cp, p2_stress, p2_qol,
|
| 962 |
-
tasks_display, task_select, spectators_display, your_turn_msg]
|
| 963 |
)
|
| 964 |
|
| 965 |
deep_rest_btn.click(
|
| 966 |
lambda s: execute_rest_handler(s, "deep_rest"),
|
| 967 |
inputs=[session_state],
|
| 968 |
-
outputs=[action_result]
|
| 969 |
).then(
|
| 970 |
refresh_game_state,
|
| 971 |
inputs=[session_state],
|
| 972 |
-
outputs=[status_display,
|
| 973 |
-
|
| 974 |
-
p2_name, p2_character, p2_time, p2_money, p2_hp, p2_cp, p2_stress, p2_qol,
|
| 975 |
-
tasks_display, task_select, spectators_display, your_turn_msg]
|
| 976 |
)
|
| 977 |
|
| 978 |
solo_task_btn.click(
|
| 979 |
execute_solo_task_handler,
|
| 980 |
inputs=[session_state, task_select],
|
| 981 |
-
outputs=[action_result]
|
| 982 |
).then(
|
| 983 |
refresh_game_state,
|
| 984 |
inputs=[session_state],
|
| 985 |
-
outputs=[status_display,
|
| 986 |
-
|
| 987 |
-
p2_name, p2_character, p2_time, p2_money, p2_hp, p2_cp, p2_stress, p2_qol,
|
| 988 |
-
tasks_display, task_select, spectators_display, your_turn_msg]
|
| 989 |
)
|
| 990 |
|
| 991 |
end_turn_btn.click(
|
| 992 |
end_turn_handler,
|
| 993 |
inputs=[session_state],
|
| 994 |
-
outputs=[action_result]
|
| 995 |
).then(
|
| 996 |
refresh_game_state,
|
| 997 |
inputs=[session_state],
|
| 998 |
-
outputs=[status_display,
|
| 999 |
-
|
| 1000 |
-
p2_name, p2_character, p2_time, p2_money, p2_hp, p2_cp, p2_stress, p2_qol,
|
| 1001 |
-
tasks_display, task_select, spectators_display, your_turn_msg]
|
| 1002 |
)
|
| 1003 |
|
| 1004 |
refresh_rankings_btn.click(
|
|
@@ -1006,22 +1049,16 @@ def create_ui():
|
|
| 1006 |
outputs=[rankings_table]
|
| 1007 |
)
|
| 1008 |
|
| 1009 |
-
# Auto-
|
| 1010 |
app.load(load_rankings, outputs=[rankings_table])
|
| 1011 |
|
| 1012 |
return app
|
| 1013 |
|
| 1014 |
# ===== MAIN =====
|
| 1015 |
if __name__ == "__main__":
|
| 1016 |
-
print("π Life Frontier: Partner's Concerto")
|
| 1017 |
-
print("=" * 50)
|
| 1018 |
-
print("Starting server...")
|
| 1019 |
-
|
| 1020 |
app = create_ui()
|
| 1021 |
app.launch(
|
| 1022 |
server_name="0.0.0.0",
|
| 1023 |
server_port=7860,
|
| 1024 |
-
share=False
|
| 1025 |
-
show_error=True
|
| 1026 |
)
|
| 1027 |
-
print("\n⨠Server started! Open http://localhost:7860")
|
|
|
|
| 1 |
+
"""# ===== app.py - Single Gradio App with SQLite =====
|
| 2 |
"""
|
| 3 |
Life Frontier: Partner's Concerto
|
| 4 |
Single-app turn-based game with SQLite state management
|
|
|
|
| 551 |
|
| 552 |
def execute_solo_task(player: Dict, task: Dict) -> Tuple[bool, str, Dict]:
|
| 553 |
"""Execute task solo"""
|
| 554 |
+
# Check affordability
|
| 555 |
+
can_afford, msg = can_afford_task(player, task, is_solo=True)
|
| 556 |
+
if not can_afford:
|
| 557 |
+
return False, msg, {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
|
| 559 |
+
solo_req = task['solo']
|
|
|
|
|
|
|
| 560 |
|
| 561 |
# Execute
|
| 562 |
updates = {
|
|
|
|
| 588 |
updates['stress'] = max(0, updates.get('stress', player['stress']) + value)
|
| 589 |
elif eff_type == 'hp':
|
| 590 |
updates['hp'] = min(updates.get('hp', player['hp']) + value, player['hp_max'])
|
| 591 |
+
elif eff_type == 'cp':
|
| 592 |
+
updates['cp'] = min(updates.get('cp', player['cp']) + value, player['cp_max'])
|
| 593 |
elif eff_type == 'hp_max':
|
| 594 |
updates['hp_max'] = player['hp_max'] + value
|
| 595 |
+
elif eff_type == 'cp_max':
|
| 596 |
+
updates['cp_max'] = player['cp_max'] + value
|
| 597 |
elif effect == 'recover_all':
|
| 598 |
updates['hp'] = player['hp_max']
|
| 599 |
updates['cp'] = player['cp_max']
|
|
|
|
| 601 |
# Check elimination
|
| 602 |
if updates.get('stress', player['stress']) >= STRESS_THRESHOLD:
|
| 603 |
updates['is_eliminated'] = 1
|
| 604 |
+
return True, f"β οΈ Task completed but ELIMINATED (stress reached {updates['stress']})", updates
|
| 605 |
|
| 606 |
+
# Build success message
|
| 607 |
+
msg_parts = [f"β {task['name']} completed!"]
|
| 608 |
+
if 'reward' in task and task['reward'] > 0:
|
| 609 |
+
msg_parts.append(f"+${task['reward']}")
|
| 610 |
+
if 'qol' in task and task['qol'] > 0:
|
| 611 |
+
msg_parts.append(f"+{task['qol']} QoL")
|
| 612 |
+
if 'effect' in task and 'stress:-' in task['effect']:
|
| 613 |
+
msg_parts.append(f"Stress reduced!")
|
| 614 |
+
|
| 615 |
+
return True, " ".join(msg_parts), updates
|
| 616 |
+
|
| 617 |
+
def initiate_negotiation(initiator: Dict, partner: Dict, task: Dict,
|
| 618 |
+
initiator_contrib: int) -> Tuple[bool, str, Dict]:
|
| 619 |
+
"""Initiate cooperation negotiation"""
|
| 620 |
+
# Check initiator can afford their contribution
|
| 621 |
+
if initiator['cp'] < initiator_contrib:
|
| 622 |
+
return False, "Not enough CP to contribute", {}
|
| 623 |
+
|
| 624 |
+
if initiator['time'] < task['time']:
|
| 625 |
+
return False, f"Not enough time (need {task['time']}h)", {}
|
| 626 |
+
|
| 627 |
+
# Check task has coop requirements
|
| 628 |
+
if 'coop' not in task:
|
| 629 |
+
return False, "This task doesn't support cooperation", {}
|
| 630 |
+
|
| 631 |
+
# Calculate target for partner
|
| 632 |
+
coop_req = task['coop']
|
| 633 |
+
total_needed = list(coop_req.values())[0] # Get first requirement value
|
| 634 |
+
partner_target = total_needed - initiator_contrib
|
| 635 |
+
|
| 636 |
+
if partner_target <= 0:
|
| 637 |
+
return False, "You're already contributing enough! Just do it solo.", {}
|
| 638 |
+
|
| 639 |
+
# Save negotiation state
|
| 640 |
+
negotiation_data = {
|
| 641 |
+
'task_id': task['id'],
|
| 642 |
+
'initiator_contrib': initiator_contrib,
|
| 643 |
+
'partner_target': partner_target,
|
| 644 |
+
'total_needed': total_needed,
|
| 645 |
+
'pending': True
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
return True, f"Negotiation started! Partner needs to contribute {partner_target} points.", negotiation_data
|
| 649 |
+
|
| 650 |
+
def respond_negotiation(partner: Dict, negotiation: Dict,
|
| 651 |
+
cp_invest: int, hp_invest: int) -> Tuple[bool, str, int]:
|
| 652 |
+
"""Partner responds to negotiation"""
|
| 653 |
+
if cp_invest < 1:
|
| 654 |
+
return False, "Must invest at least 1 CP", 0
|
| 655 |
+
|
| 656 |
+
if partner['cp'] < cp_invest:
|
| 657 |
+
return False, f"Not enough CP (need {cp_invest})", 0
|
| 658 |
+
|
| 659 |
+
if partner['hp'] < hp_invest:
|
| 660 |
+
return False, f"Not enough HP (need {hp_invest})", 0
|
| 661 |
|
| 662 |
+
# Calculate result using formula
|
| 663 |
+
target = negotiation['partner_target']
|
| 664 |
+
investment = cp_invest + hp_invest
|
| 665 |
+
|
| 666 |
+
# Simple formula: (target Γ investment) / investment = target
|
| 667 |
+
# But if investment < target, get proportional result
|
| 668 |
+
actual_contrib = min(target, investment)
|
| 669 |
+
|
| 670 |
+
# Round up if close
|
| 671 |
+
if actual_contrib >= target * 0.9:
|
| 672 |
+
actual_contrib = target
|
| 673 |
+
|
| 674 |
+
return True, f"Contributed {actual_contrib} points", actual_contrib
|
| 675 |
|
| 676 |
# ===== GRADIO UI =====
|
| 677 |
def create_ui():
|
|
|
|
| 714 |
# Player 1 Panel
|
| 715 |
with gr.Column():
|
| 716 |
gr.Markdown("### Player 1")
|
| 717 |
+
p1_name = gr.Textbox(label="Name", interactive=False)
|
| 718 |
+
p1_character = gr.Textbox(label="Character", interactive=False)
|
| 719 |
+
with gr.Row():
|
| 720 |
+
p1_time = gr.Number(label="β° Time", interactive=False)
|
| 721 |
+
p1_money = gr.Number(label="π° Money", interactive=False)
|
| 722 |
+
with gr.Row():
|
| 723 |
+
p1_hp = gr.Number(label="β€οΈ HP", interactive=False)
|
| 724 |
+
p1_cp = gr.Number(label="π§ CP", interactive=False)
|
| 725 |
+
with gr.Row():
|
| 726 |
+
p1_stress = gr.Number(label="π° Stress", interactive=False)
|
| 727 |
+
p1_qol = gr.Number(label="β¨ QoL", interactive=False)
|
| 728 |
|
| 729 |
# Center - Actions
|
| 730 |
with gr.Column():
|
|
|
|
| 733 |
|
| 734 |
gr.Markdown("**Rest Actions**")
|
| 735 |
with gr.Row():
|
| 736 |
+
quick_nap_btn = gr.Button("π Quick Nap (3h)", size="sm")
|
| 737 |
+
full_sleep_btn = gr.Button("π΄ Full Sleep (9h)", size="sm")
|
| 738 |
+
deep_rest_btn = gr.Button("π€ Deep Rest (12h)", size="sm")
|
| 739 |
|
| 740 |
gr.Markdown("**Available Tasks**")
|
| 741 |
tasks_display = gr.Dataframe(
|
| 742 |
+
headers=["ID", "Name", "Type", "Time", "Req (Solo)", "Req (Coop)", "Reward"],
|
| 743 |
+
label="Public Tasks",
|
| 744 |
+
wrap=True
|
| 745 |
)
|
| 746 |
|
| 747 |
+
task_select = gr.Dropdown(label="Select Task", choices=[])
|
|
|
|
| 748 |
|
| 749 |
+
with gr.Row():
|
| 750 |
+
solo_task_btn = gr.Button("βΆοΈ Execute Solo", variant="primary")
|
| 751 |
+
coop_task_btn = gr.Button("π€ Propose Cooperation", variant="secondary")
|
| 752 |
+
|
| 753 |
+
# Cooperation panel (initially hidden)
|
| 754 |
+
with gr.Group(visible=False) as coop_panel:
|
| 755 |
+
gr.Markdown("### π€ Cooperation Proposal")
|
| 756 |
+
coop_contrib_slider = gr.Slider(1, 10, value=3, step=1,
|
| 757 |
+
label="Your CP Contribution")
|
| 758 |
+
send_proposal_btn = gr.Button("π€ Send Proposal to Partner")
|
| 759 |
+
|
| 760 |
+
# Negotiation response (for partner)
|
| 761 |
+
with gr.Group(visible=False) as negotiation_panel:
|
| 762 |
+
gr.Markdown("### π€ Partner's Cooperation Request")
|
| 763 |
+
negotiation_info = gr.Markdown()
|
| 764 |
+
with gr.Row():
|
| 765 |
+
response_cp = gr.Slider(1, 10, value=1, step=1, label="Your CP Investment")
|
| 766 |
+
response_hp = gr.Slider(0, 5, value=0, step=1, label="Your HP Investment")
|
| 767 |
+
with gr.Row():
|
| 768 |
+
accept_coop_btn = gr.Button("β
Accept", variant="primary")
|
| 769 |
+
reject_coop_btn = gr.Button("β Reject", variant="stop")
|
| 770 |
+
|
| 771 |
+
end_turn_btn = gr.Button("βοΈ End Turn", variant="secondary", size="lg")
|
| 772 |
|
| 773 |
action_result = gr.Markdown()
|
| 774 |
|
| 775 |
# Player 2 Panel
|
| 776 |
with gr.Column():
|
| 777 |
gr.Markdown("### Player 2")
|
| 778 |
+
p2_name = gr.Textbox(label="Name", interactive=False)
|
| 779 |
+
p2_character = gr.Textbox(label="Character", interactive=False)
|
| 780 |
+
with gr.Row():
|
| 781 |
+
p2_time = gr.Number(label="β° Time", interactive=False)
|
| 782 |
+
p2_money = gr.Number(label="π° Money", interactive=False)
|
| 783 |
+
with gr.Row():
|
| 784 |
+
p2_hp = gr.Number(label="β€οΈ HP", interactive=False)
|
| 785 |
+
p2_cp = gr.Number(label="π§ CP", interactive=False)
|
| 786 |
+
with gr.Row():
|
| 787 |
+
p2_stress = gr.Number(label="π° Stress", interactive=False)
|
| 788 |
+
p2_qol = gr.Number(label="β¨ QoL", interactive=False)
|
| 789 |
|
| 790 |
gr.Markdown("### Spectators")
|
| 791 |
spectators_display = gr.Markdown("*No spectators*")
|
| 792 |
|
| 793 |
+
refresh_btn = gr.Button("π Refresh Game State", variant="secondary")
|
| 794 |
|
| 795 |
with gr.Tab("π Rankings"):
|
| 796 |
rankings_table = gr.Dataframe(
|
|
|
|
| 835 |
|
| 836 |
def refresh_game_state(session):
|
| 837 |
if not session.get('room_id'):
|
| 838 |
+
return ("*No game joined*", {}, {}, [], "*No spectators*", "")
|
|
|
|
| 839 |
|
| 840 |
state = db.get_room_state(session['room_id'])
|
| 841 |
if not state:
|
| 842 |
+
return ("*Room not found*", {}, {}, [], "*No spectators*", "")
|
|
|
|
| 843 |
|
| 844 |
room = state['room']
|
| 845 |
players = state['players']
|
| 846 |
|
| 847 |
# Status
|
| 848 |
status_md = f"""
|
| 849 |
+
**Room:** {session['room_id']} | **Round:** {room['current_round']}/{room['total_rounds']}
|
| 850 |
**Status:** {room['status'].upper()} | **Current Turn:** Player {room['current_turn'] + 1}
|
| 851 |
"""
|
| 852 |
|
| 853 |
if room['status'] == 'finished':
|
| 854 |
+
status_md += f"\n\nπ **GAME OVER!** Winner: {room['winner_id']}"
|
|
|
|
| 855 |
|
| 856 |
+
# Players
|
| 857 |
p1_data = players[0] if len(players) > 0 else {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 858 |
p2_data = players[1] if len(players) > 1 else {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 859 |
|
| 860 |
+
# Tasks
|
| 861 |
task_rows = []
|
|
|
|
| 862 |
for task_id in state['task_ids']:
|
| 863 |
task = next((t for t in TASKS if t['id'] == task_id), None)
|
| 864 |
if task:
|
| 865 |
+
req_str = ', '.join([f"{k}:{v}" for k, v in task['solo'].items()])
|
| 866 |
+
reward_str = f"${task.get('reward', 0)}" if 'reward' in task else f"{task.get('qol', 0)} QoL"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 867 |
task_rows.append([
|
| 868 |
task['id'], task['name'], task['type'],
|
| 869 |
+
f"{task['time']}h", req_str, reward_str
|
| 870 |
])
|
| 871 |
+
|
| 872 |
+
# Task dropdown options
|
| 873 |
+
task_options = [(f"{t['id']} - {t['name']}", t['id']) for t in TASKS if t['id'] in state['task_ids']]
|
| 874 |
|
| 875 |
# Spectators
|
| 876 |
spec_md = f"π₯ {len(state['spectators'])} watching: {', '.join(state['spectators'])}" if state['spectators'] else "*No spectators*"
|
| 877 |
|
| 878 |
# Turn indicator
|
| 879 |
if session.get('is_spectator'):
|
| 880 |
+
turn_msg = "ποΈ You are spectating"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 881 |
elif session.get('player_slot') == room['current_turn']:
|
| 882 |
turn_msg = "β
**YOUR TURN!** Take an action below."
|
| 883 |
else:
|
| 884 |
+
turn_msg = "β³ Waiting for partner's turn..."
|
| 885 |
|
| 886 |
+
return (status_md, p1_data, p2_data, task_rows, spec_md, turn_msg)
|
| 887 |
|
| 888 |
def execute_rest_handler(session, rest_type):
|
| 889 |
if not session.get('room_id') or session.get('is_spectator'):
|
| 890 |
+
return "β Not in game as player", session
|
| 891 |
|
| 892 |
state = db.get_room_state(session['room_id'])
|
| 893 |
if not state:
|
| 894 |
+
return "β Room not found", session
|
| 895 |
|
| 896 |
room = state['room']
|
|
|
|
|
|
|
|
|
|
| 897 |
if room['current_turn'] != session['player_slot']:
|
| 898 |
+
return "β Not your turn!", session
|
| 899 |
|
| 900 |
player = state['players'][session['player_slot']]
|
| 901 |
success, msg, updates = execute_rest(player, rest_type)
|
| 902 |
|
| 903 |
if success:
|
| 904 |
db.update_player(session['room_id'], session['player_slot'], updates)
|
| 905 |
+
return f"β
{msg}", session
|
| 906 |
|
| 907 |
+
return f"β {msg}", session
|
| 908 |
|
| 909 |
def execute_solo_task_handler(session, task_id):
|
| 910 |
if not session.get('room_id') or session.get('is_spectator'):
|
| 911 |
+
return "β Not in game as player", session
|
| 912 |
|
| 913 |
if not task_id:
|
| 914 |
+
return "β Select a task first", session
|
| 915 |
|
| 916 |
state = db.get_room_state(session['room_id'])
|
| 917 |
if not state:
|
| 918 |
+
return "β Room not found", session
|
| 919 |
|
| 920 |
room = state['room']
|
|
|
|
|
|
|
|
|
|
| 921 |
if room['current_turn'] != session['player_slot']:
|
| 922 |
+
return "β Not your turn!", session
|
| 923 |
|
| 924 |
player = state['players'][session['player_slot']]
|
| 925 |
task = next((t for t in TASKS if t['id'] == task_id), None)
|
| 926 |
|
| 927 |
if not task:
|
| 928 |
+
return "β Task not found", session
|
| 929 |
|
| 930 |
success, msg, updates = execute_solo_task(player, task)
|
| 931 |
|
|
|
|
| 936 |
updates['completed_tasks'] = json.dumps(completed)
|
| 937 |
|
| 938 |
db.update_player(session['room_id'], session['player_slot'], updates)
|
| 939 |
+
return f"β
{msg}", session
|
| 940 |
|
| 941 |
+
return f"β {msg}", session
|
| 942 |
|
| 943 |
def end_turn_handler(session):
|
| 944 |
if not session.get('room_id') or session.get('is_spectator'):
|
| 945 |
+
return "β Not in game as player", session
|
| 946 |
|
| 947 |
state = db.get_room_state(session['room_id'])
|
| 948 |
if not state:
|
| 949 |
+
return "β Room not found", session
|
| 950 |
|
| 951 |
room = state['room']
|
|
|
|
|
|
|
|
|
|
| 952 |
if room['current_turn'] != session['player_slot']:
|
| 953 |
+
return "β Not your turn!", session
|
| 954 |
|
| 955 |
db.end_turn(session['room_id'], session['player_slot'])
|
| 956 |
+
return "β
Turn ended. Waiting for partner...", session
|
| 957 |
|
| 958 |
def load_rankings():
|
| 959 |
rankings = db.get_rankings(20)
|
|
|
|
| 985 |
refresh_btn.click(
|
| 986 |
refresh_game_state,
|
| 987 |
inputs=[session_state],
|
| 988 |
+
outputs=[status_display, p1_display, p2_display, tasks_display,
|
| 989 |
+
spectators_display, your_turn_msg]
|
|
|
|
|
|
|
| 990 |
)
|
| 991 |
|
| 992 |
quick_nap_btn.click(
|
| 993 |
lambda s: execute_rest_handler(s, "quick_nap"),
|
| 994 |
inputs=[session_state],
|
| 995 |
+
outputs=[action_result, session_state]
|
| 996 |
).then(
|
| 997 |
refresh_game_state,
|
| 998 |
inputs=[session_state],
|
| 999 |
+
outputs=[status_display, p1_display, p2_display, tasks_display,
|
| 1000 |
+
spectators_display, your_turn_msg]
|
|
|
|
|
|
|
| 1001 |
)
|
| 1002 |
|
| 1003 |
full_sleep_btn.click(
|
| 1004 |
lambda s: execute_rest_handler(s, "full_sleep"),
|
| 1005 |
inputs=[session_state],
|
| 1006 |
+
outputs=[action_result, session_state]
|
| 1007 |
).then(
|
| 1008 |
refresh_game_state,
|
| 1009 |
inputs=[session_state],
|
| 1010 |
+
outputs=[status_display, p1_display, p2_display, tasks_display,
|
| 1011 |
+
spectators_display, your_turn_msg]
|
|
|
|
|
|
|
| 1012 |
)
|
| 1013 |
|
| 1014 |
deep_rest_btn.click(
|
| 1015 |
lambda s: execute_rest_handler(s, "deep_rest"),
|
| 1016 |
inputs=[session_state],
|
| 1017 |
+
outputs=[action_result, session_state]
|
| 1018 |
).then(
|
| 1019 |
refresh_game_state,
|
| 1020 |
inputs=[session_state],
|
| 1021 |
+
outputs=[status_display, p1_display, p2_display, tasks_display,
|
| 1022 |
+
spectators_display, your_turn_msg]
|
|
|
|
|
|
|
| 1023 |
)
|
| 1024 |
|
| 1025 |
solo_task_btn.click(
|
| 1026 |
execute_solo_task_handler,
|
| 1027 |
inputs=[session_state, task_select],
|
| 1028 |
+
outputs=[action_result, session_state]
|
| 1029 |
).then(
|
| 1030 |
refresh_game_state,
|
| 1031 |
inputs=[session_state],
|
| 1032 |
+
outputs=[status_display, p1_display, p2_display, tasks_display,
|
| 1033 |
+
spectators_display, your_turn_msg]
|
|
|
|
|
|
|
| 1034 |
)
|
| 1035 |
|
| 1036 |
end_turn_btn.click(
|
| 1037 |
end_turn_handler,
|
| 1038 |
inputs=[session_state],
|
| 1039 |
+
outputs=[action_result, session_state]
|
| 1040 |
).then(
|
| 1041 |
refresh_game_state,
|
| 1042 |
inputs=[session_state],
|
| 1043 |
+
outputs=[status_display, p1_display, p2_display, tasks_display,
|
| 1044 |
+
spectators_display, your_turn_msg]
|
|
|
|
|
|
|
| 1045 |
)
|
| 1046 |
|
| 1047 |
refresh_rankings_btn.click(
|
|
|
|
| 1049 |
outputs=[rankings_table]
|
| 1050 |
)
|
| 1051 |
|
| 1052 |
+
# Auto-refresh on tab switch
|
| 1053 |
app.load(load_rankings, outputs=[rankings_table])
|
| 1054 |
|
| 1055 |
return app
|
| 1056 |
|
| 1057 |
# ===== MAIN =====
|
| 1058 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1059 |
app = create_ui()
|
| 1060 |
app.launch(
|
| 1061 |
server_name="0.0.0.0",
|
| 1062 |
server_port=7860,
|
| 1063 |
+
share=False
|
|
|
|
| 1064 |
)
|
|
|