Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
"""
|
| 2 |
Life Frontier: Partner's Concerto
|
| 3 |
Single-app turn-based game with SQLite state management
|
|
@@ -166,12 +167,16 @@ class GameDatabase:
|
|
| 166 |
|
| 167 |
def create_room(self, room_id: str, total_rounds: int = 8) -> bool:
|
| 168 |
with self.lock, self.get_connection() as conn:
|
|
|
|
|
|
|
|
|
|
| 169 |
try:
|
| 170 |
conn.execute("""
|
| 171 |
INSERT INTO rooms (room_id, total_rounds, created_at, status)
|
| 172 |
VALUES (?, ?, ?, 'waiting')
|
| 173 |
""", (room_id, total_rounds, datetime.now().isoformat()))
|
| 174 |
conn.commit()
|
|
|
|
| 175 |
return True
|
| 176 |
except sqlite3.IntegrityError:
|
| 177 |
return False
|
|
@@ -218,13 +223,15 @@ class GameDatabase:
|
|
| 218 |
room_id, player_id, name, character_id, player_slot
|
| 219 |
) VALUES (?, ?, ?, ?, ?)
|
| 220 |
""", (room_id, player_id, name, character_id, slot))
|
| 221 |
-
conn.commit()
|
| 222 |
|
| 223 |
# If 2 players, start game
|
| 224 |
if slot == 1:
|
| 225 |
self._start_game(room_id, conn)
|
|
|
|
|
|
|
| 226 |
|
| 227 |
-
|
|
|
|
| 228 |
|
| 229 |
def _start_game(self, room_id: str, conn):
|
| 230 |
"""Internal: Start game when 2 players joined"""
|
|
@@ -239,7 +246,44 @@ class GameDatabase:
|
|
| 239 |
UPDATE rooms SET available_tasks = ? WHERE room_id = ?
|
| 240 |
""", (json.dumps(task_ids), room_id))
|
| 241 |
|
| 242 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
def get_room_state(self, room_id: str) -> Optional[Dict]:
|
| 245 |
"""Get complete room state including players"""
|
|
@@ -452,6 +496,24 @@ db = GameDatabase()
|
|
| 452 |
def roll_dice() -> int:
|
| 453 |
return random.randint(1, 6)
|
| 454 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
def execute_rest(player: Dict, rest_type: str) -> Tuple[bool, str, Dict]:
|
| 456 |
"""Execute rest action"""
|
| 457 |
if rest_type == "quick_nap":
|
|
@@ -673,97 +735,139 @@ def create_ui():
|
|
| 673 |
|
| 674 |
def refresh_game_state(session):
|
| 675 |
if not session.get('room_id'):
|
| 676 |
-
return ("*No game joined*",
|
|
|
|
| 677 |
|
| 678 |
state = db.get_room_state(session['room_id'])
|
| 679 |
if not state:
|
| 680 |
-
return ("*Room not found*",
|
|
|
|
| 681 |
|
| 682 |
room = state['room']
|
| 683 |
players = state['players']
|
| 684 |
|
| 685 |
# Status
|
| 686 |
status_md = f"""
|
| 687 |
-
**Room:** {session['room_id']} | **Round:** {room['current_round']}/{room['total_rounds']}
|
| 688 |
**Status:** {room['status'].upper()} | **Current Turn:** Player {room['current_turn'] + 1}
|
| 689 |
"""
|
| 690 |
|
| 691 |
if room['status'] == 'finished':
|
| 692 |
-
|
|
|
|
| 693 |
|
| 694 |
-
#
|
| 695 |
p1_data = players[0] if len(players) > 0 else {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 696 |
p2_data = players[1] if len(players) > 1 else {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 697 |
|
| 698 |
-
# Tasks
|
| 699 |
task_rows = []
|
|
|
|
| 700 |
for task_id in state['task_ids']:
|
| 701 |
task = next((t for t in TASKS if t['id'] == task_id), None)
|
| 702 |
if task:
|
| 703 |
-
|
| 704 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 705 |
task_rows.append([
|
| 706 |
task['id'], task['name'], task['type'],
|
| 707 |
-
f"{task['time']}h",
|
| 708 |
])
|
| 709 |
-
|
| 710 |
-
# Task dropdown options
|
| 711 |
-
task_options = [(f"{t['id']} - {t['name']}", t['id']) for t in TASKS if t['id'] in state['task_ids']]
|
| 712 |
|
| 713 |
# Spectators
|
| 714 |
spec_md = f"👥 {len(state['spectators'])} watching: {', '.join(state['spectators'])}" if state['spectators'] else "*No spectators*"
|
| 715 |
|
| 716 |
# Turn indicator
|
| 717 |
if session.get('is_spectator'):
|
| 718 |
-
turn_msg = "👁️ You are spectating"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 719 |
elif session.get('player_slot') == room['current_turn']:
|
| 720 |
turn_msg = "✅ **YOUR TURN!** Take an action below."
|
| 721 |
else:
|
| 722 |
-
turn_msg = "⏳
|
| 723 |
|
| 724 |
-
return (status_md,
|
| 725 |
|
| 726 |
def execute_rest_handler(session, rest_type):
|
| 727 |
if not session.get('room_id') or session.get('is_spectator'):
|
| 728 |
-
return "❌ Not in game as player"
|
| 729 |
|
| 730 |
state = db.get_room_state(session['room_id'])
|
| 731 |
if not state:
|
| 732 |
-
return "❌ Room not found"
|
| 733 |
|
| 734 |
room = state['room']
|
|
|
|
|
|
|
|
|
|
| 735 |
if room['current_turn'] != session['player_slot']:
|
| 736 |
-
return "❌ Not your turn!"
|
| 737 |
|
| 738 |
player = state['players'][session['player_slot']]
|
| 739 |
success, msg, updates = execute_rest(player, rest_type)
|
| 740 |
|
| 741 |
if success:
|
| 742 |
db.update_player(session['room_id'], session['player_slot'], updates)
|
| 743 |
-
return f"✅ {msg}"
|
| 744 |
|
| 745 |
-
return f"❌ {msg}"
|
| 746 |
|
| 747 |
def execute_solo_task_handler(session, task_id):
|
| 748 |
if not session.get('room_id') or session.get('is_spectator'):
|
| 749 |
-
return "❌ Not in game as player"
|
| 750 |
|
| 751 |
if not task_id:
|
| 752 |
-
return "❌ Select a task first"
|
| 753 |
|
| 754 |
state = db.get_room_state(session['room_id'])
|
| 755 |
if not state:
|
| 756 |
-
return "❌ Room not found"
|
| 757 |
|
| 758 |
room = state['room']
|
|
|
|
|
|
|
|
|
|
| 759 |
if room['current_turn'] != session['player_slot']:
|
| 760 |
-
return "❌ Not your turn!"
|
| 761 |
|
| 762 |
player = state['players'][session['player_slot']]
|
| 763 |
task = next((t for t in TASKS if t['id'] == task_id), None)
|
| 764 |
|
| 765 |
if not task:
|
| 766 |
-
return "❌ Task not found"
|
| 767 |
|
| 768 |
success, msg, updates = execute_solo_task(player, task)
|
| 769 |
|
|
@@ -774,24 +878,27 @@ def create_ui():
|
|
| 774 |
updates['completed_tasks'] = json.dumps(completed)
|
| 775 |
|
| 776 |
db.update_player(session['room_id'], session['player_slot'], updates)
|
| 777 |
-
return f"✅ {msg}"
|
| 778 |
|
| 779 |
-
return f"❌ {msg}"
|
| 780 |
|
| 781 |
def end_turn_handler(session):
|
| 782 |
if not session.get('room_id') or session.get('is_spectator'):
|
| 783 |
-
return "❌ Not in game as player"
|
| 784 |
|
| 785 |
state = db.get_room_state(session['room_id'])
|
| 786 |
if not state:
|
| 787 |
-
return "❌ Room not found"
|
| 788 |
|
| 789 |
room = state['room']
|
|
|
|
|
|
|
|
|
|
| 790 |
if room['current_turn'] != session['player_slot']:
|
| 791 |
-
return "❌ Not your turn!"
|
| 792 |
|
| 793 |
db.end_turn(session['room_id'], session['player_slot'])
|
| 794 |
-
return "✅ Turn ended. Waiting for partner..."
|
| 795 |
|
| 796 |
def load_rankings():
|
| 797 |
rankings = db.get_rankings(20)
|
|
@@ -823,63 +930,75 @@ def create_ui():
|
|
| 823 |
refresh_btn.click(
|
| 824 |
refresh_game_state,
|
| 825 |
inputs=[session_state],
|
| 826 |
-
outputs=[status_display,
|
| 827 |
-
|
|
|
|
|
|
|
| 828 |
)
|
| 829 |
|
| 830 |
quick_nap_btn.click(
|
| 831 |
lambda s: execute_rest_handler(s, "quick_nap"),
|
| 832 |
inputs=[session_state],
|
| 833 |
-
outputs=[action_result
|
| 834 |
).then(
|
| 835 |
refresh_game_state,
|
| 836 |
inputs=[session_state],
|
| 837 |
-
outputs=[status_display,
|
| 838 |
-
|
|
|
|
|
|
|
| 839 |
)
|
| 840 |
|
| 841 |
full_sleep_btn.click(
|
| 842 |
lambda s: execute_rest_handler(s, "full_sleep"),
|
| 843 |
inputs=[session_state],
|
| 844 |
-
outputs=[action_result
|
| 845 |
).then(
|
| 846 |
refresh_game_state,
|
| 847 |
inputs=[session_state],
|
| 848 |
-
outputs=[status_display,
|
| 849 |
-
|
|
|
|
|
|
|
| 850 |
)
|
| 851 |
|
| 852 |
deep_rest_btn.click(
|
| 853 |
lambda s: execute_rest_handler(s, "deep_rest"),
|
| 854 |
inputs=[session_state],
|
| 855 |
-
outputs=[action_result
|
| 856 |
).then(
|
| 857 |
refresh_game_state,
|
| 858 |
inputs=[session_state],
|
| 859 |
-
outputs=[status_display,
|
| 860 |
-
|
|
|
|
|
|
|
| 861 |
)
|
| 862 |
|
| 863 |
solo_task_btn.click(
|
| 864 |
execute_solo_task_handler,
|
| 865 |
inputs=[session_state, task_select],
|
| 866 |
-
outputs=[action_result
|
| 867 |
).then(
|
| 868 |
refresh_game_state,
|
| 869 |
inputs=[session_state],
|
| 870 |
-
outputs=[status_display,
|
| 871 |
-
|
|
|
|
|
|
|
| 872 |
)
|
| 873 |
|
| 874 |
end_turn_btn.click(
|
| 875 |
end_turn_handler,
|
| 876 |
inputs=[session_state],
|
| 877 |
-
outputs=[action_result
|
| 878 |
).then(
|
| 879 |
refresh_game_state,
|
| 880 |
inputs=[session_state],
|
| 881 |
-
outputs=[status_display,
|
| 882 |
-
|
|
|
|
|
|
|
| 883 |
)
|
| 884 |
|
| 885 |
refresh_rankings_btn.click(
|
|
@@ -887,16 +1006,22 @@ def create_ui():
|
|
| 887 |
outputs=[rankings_table]
|
| 888 |
)
|
| 889 |
|
| 890 |
-
# Auto-
|
| 891 |
app.load(load_rankings, outputs=[rankings_table])
|
| 892 |
|
| 893 |
return app
|
| 894 |
|
| 895 |
# ===== MAIN =====
|
| 896 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 897 |
app = create_ui()
|
| 898 |
app.launch(
|
| 899 |
server_name="0.0.0.0",
|
| 900 |
server_port=7860,
|
| 901 |
-
share=False
|
| 902 |
-
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
| 167 |
|
| 168 |
def create_room(self, room_id: str, total_rounds: int = 8) -> bool:
|
| 169 |
with self.lock, self.get_connection() as conn:
|
| 170 |
+
# Cleanup old rooms before creating new one
|
| 171 |
+
self.cleanup_old_rooms()
|
| 172 |
+
|
| 173 |
try:
|
| 174 |
conn.execute("""
|
| 175 |
INSERT INTO rooms (room_id, total_rounds, created_at, status)
|
| 176 |
VALUES (?, ?, ?, 'waiting')
|
| 177 |
""", (room_id, total_rounds, datetime.now().isoformat()))
|
| 178 |
conn.commit()
|
| 179 |
+
print(f"✨ Created room {room_id}")
|
| 180 |
return True
|
| 181 |
except sqlite3.IntegrityError:
|
| 182 |
return False
|
|
|
|
| 223 |
room_id, player_id, name, character_id, player_slot
|
| 224 |
) VALUES (?, ?, ?, ?, ?)
|
| 225 |
""", (room_id, player_id, name, character_id, slot))
|
|
|
|
| 226 |
|
| 227 |
# If 2 players, start game
|
| 228 |
if slot == 1:
|
| 229 |
self._start_game(room_id, conn)
|
| 230 |
+
conn.commit()
|
| 231 |
+
return True, slot, f"✅ Joined as Player {slot + 1}! Game starting..."
|
| 232 |
|
| 233 |
+
conn.commit()
|
| 234 |
+
return True, slot, f"Joined as Player {slot + 1}. Waiting for Player 2..."
|
| 235 |
|
| 236 |
def _start_game(self, room_id: str, conn):
|
| 237 |
"""Internal: Start game when 2 players joined"""
|
|
|
|
| 246 |
UPDATE rooms SET available_tasks = ? WHERE room_id = ?
|
| 247 |
""", (json.dumps(task_ids), room_id))
|
| 248 |
|
| 249 |
+
print(f"🎮 Game started in room {room_id}!")
|
| 250 |
+
|
| 251 |
+
def cleanup_old_rooms(self):
|
| 252 |
+
"""Remove empty or stale rooms (no activity for 1 day)"""
|
| 253 |
+
with self.lock, self.get_connection() as conn:
|
| 254 |
+
cutoff_time = datetime.now().timestamp() - 86400 # 24 hours ago
|
| 255 |
+
|
| 256 |
+
# Find stale rooms
|
| 257 |
+
stale_rooms = conn.execute("""
|
| 258 |
+
SELECT room_id FROM rooms
|
| 259 |
+
WHERE status = 'waiting'
|
| 260 |
+
AND strftime('%s', created_at) < ?
|
| 261 |
+
""", (cutoff_time,)).fetchall()
|
| 262 |
+
|
| 263 |
+
# Also find empty rooms (no players after 1 hour)
|
| 264 |
+
empty_cutoff = datetime.now().timestamp() - 3600 # 1 hour ago
|
| 265 |
+
empty_rooms = conn.execute("""
|
| 266 |
+
SELECT r.room_id FROM rooms r
|
| 267 |
+
LEFT JOIN players p ON r.room_id = p.room_id
|
| 268 |
+
WHERE r.status = 'waiting'
|
| 269 |
+
AND p.room_id IS NULL
|
| 270 |
+
AND strftime('%s', r.created_at) < ?
|
| 271 |
+
""", (empty_cutoff,)).fetchall()
|
| 272 |
+
|
| 273 |
+
rooms_to_delete = set(
|
| 274 |
+
[r['room_id'] for r in stale_rooms] +
|
| 275 |
+
[r['room_id'] for r in empty_rooms]
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
for room_id in rooms_to_delete:
|
| 279 |
+
# Delete associated data
|
| 280 |
+
conn.execute("DELETE FROM players WHERE room_id = ?", (room_id,))
|
| 281 |
+
conn.execute("DELETE FROM spectators WHERE room_id = ?", (room_id,))
|
| 282 |
+
conn.execute("DELETE FROM rooms WHERE room_id = ?", (room_id,))
|
| 283 |
+
print(f"🧹 Cleaned up stale room: {room_id}")
|
| 284 |
+
|
| 285 |
+
conn.commit()
|
| 286 |
+
return len(rooms_to_delete)
|
| 287 |
|
| 288 |
def get_room_state(self, room_id: str) -> Optional[Dict]:
|
| 289 |
"""Get complete room state including players"""
|
|
|
|
| 496 |
def roll_dice() -> int:
|
| 497 |
return random.randint(1, 6)
|
| 498 |
|
| 499 |
+
def can_afford_task(player: Dict, task: Dict, is_solo: bool = True) -> Tuple[bool, str]:
|
| 500 |
+
"""Check if player can afford task"""
|
| 501 |
+
req = task['solo'] if is_solo else task['coop']
|
| 502 |
+
|
| 503 |
+
if player['time'] < task['time']:
|
| 504 |
+
return False, f"Not enough time (need {task['time']}h)"
|
| 505 |
+
|
| 506 |
+
for res, amount in req.items():
|
| 507 |
+
if res == "HP" and player['hp'] < amount:
|
| 508 |
+
return False, f"Not enough HP (need {amount})"
|
| 509 |
+
if res == "CP" and player['cp'] < amount:
|
| 510 |
+
return False, f"Not enough CP (need {amount})"
|
| 511 |
+
|
| 512 |
+
if 'cost' in task and player['money'] < task['cost']:
|
| 513 |
+
return False, f"Not enough money (need ${task['cost']})"
|
| 514 |
+
|
| 515 |
+
return True, "OK"
|
| 516 |
+
|
| 517 |
def execute_rest(player: Dict, rest_type: str) -> Tuple[bool, str, Dict]:
|
| 518 |
"""Execute rest action"""
|
| 519 |
if rest_type == "quick_nap":
|
|
|
|
| 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 |
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 |
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 |
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")
|