npc0 commited on
Commit
d1ce83c
Β·
verified Β·
1 Parent(s): fd10988

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +182 -145
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 requirements
555
- solo_req = task['solo']
556
- if player['time'] < task['time']:
557
- return False, f"Not enough time (need {task['time']}h)", {}
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
- # Check cost
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
- msg = f"βœ“ {task['name']} completed!"
611
- if 'reward' in task:
612
- msg += f" +${task['reward']}"
613
- if 'qol' in task:
614
- msg += f" +{task['qol']} QoL"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
615
 
616
- return True, msg, updates
 
 
 
 
 
 
 
 
 
 
 
 
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
- p1_display = gr.JSON(label="Status")
 
 
 
 
 
 
 
 
 
 
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", "Requirements", "Reward"],
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
- end_turn_btn = gr.Button("⏭️ End Turn", variant="secondary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
682
 
683
  action_result = gr.Markdown()
684
 
685
  # Player 2 Panel
686
  with gr.Column():
687
  gr.Markdown("### Player 2")
688
- p2_display = gr.JSON(label="Status")
 
 
 
 
 
 
 
 
 
 
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*", "", "", 0, 0, 0, 0, 0, 0,
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*", "", "", 0, 0, 0, 0, 0, 0,
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
- winner_name = next((p['name'] for p in players if p['player_id'] == room['winner_id']), "Unknown")
757
- status_md += f"\n\nπŸ† **GAME OVER!** Winner: **{winner_name}**"
758
 
759
- # Player 1 data
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 with better formatting
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
- solo_req = ', '.join([f"{k}:{v}" for k, v in task['solo'].items()])
792
- coop_req = ', '.join([f"{k}:{v}" for k, v in task['coop'].items()])
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", solo_req, coop_req, reward_str
804
  ])
805
- task_options.append((f"{task['id']} - {task['name']}", task['id']))
 
 
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 = "πŸ‘οΈ **You are spectating** - Refresh to see updates"
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 = "⏳ **Partner's turn...** Click Refresh to see updates."
821
 
822
- return (status_md, *p1_vals, *p2_vals, task_rows, task_options, spec_md, turn_msg)
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
- p1_name, p1_character, p1_time, p1_money, p1_hp, p1_cp, p1_stress, p1_qol,
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
- p1_name, p1_character, p1_time, p1_money, p1_hp, p1_cp, p1_stress, p1_qol,
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
- p1_name, p1_character, p1_time, p1_money, p1_hp, p1_cp, p1_stress, p1_qol,
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
- p1_name, p1_character, p1_time, p1_money, p1_hp, p1_cp, p1_stress, p1_qol,
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
- p1_name, p1_character, p1_time, p1_money, p1_hp, p1_cp, p1_stress, p1_qol,
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
- p1_name, p1_character, p1_time, p1_money, p1_hp, p1_cp, p1_stress, p1_qol,
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-load rankings on app start
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
  )