Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, Request, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| import uvicorn | |
| import random | |
| import logging | |
| app = FastAPI() | |
| # Enable CORS | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| games = {} | |
| async def create_game(request: Request): | |
| data = await request.json() | |
| pin = data.get("pin") | |
| player_name = data.get("playerName") | |
| notPlaying = data.get("notPlaying") | |
| if not pin or not player_name: | |
| if not notPlaying: | |
| raise HTTPException(status_code=400, detail="PIN and player name are required.") | |
| else: | |
| games[pin] = { | |
| "pin": pin, | |
| "players": None, | |
| "gameStarted": False, | |
| "turn": player_name, | |
| "permissions": {player_name: {"steal": True, "gain": True}} | |
| } | |
| return {"success": True, "message": "Empty game created."} | |
| if pin in games: | |
| return {"success": False, "message": "Game already exists."} | |
| # Initialize permissions as a dictionary with proper keys. | |
| games[pin] = { | |
| "pin": pin, | |
| "players": [player_name], | |
| "gameStarted": False, | |
| "turn": player_name, | |
| "currentAction": None, | |
| "currentChallenge": None, | |
| "pendingCardLoss": None, | |
| "votes": [], | |
| "permissions": {player_name: {"steal": True, "gain": True}} | |
| } | |
| return {"success": True, "message": "Game created successfully!"} | |
| async def test(): | |
| return {"success": True} | |
| async def getGames(): | |
| return {"success": True, "data": games} | |
| async def get_game_status(pin: str): | |
| game = games.get(pin) | |
| if not game: | |
| return {"success": False, "message": "Game was lost or does not exist. Please create a new game."} | |
| return {"success": True, "game": game} | |
| async def handle_action(request: Request): | |
| data = await request.json() | |
| pin = data.get("pin") | |
| player = data.get("player") | |
| action = data.get("action") | |
| target = data.get("target") | |
| response = data.get("response") | |
| challenge = data.get("challenge") | |
| if pin not in games: | |
| raise HTTPException(status_code=404, detail="Game not found.") | |
| game = games[pin] | |
| # If a challenge is pending, only allow challengeResponse (or getStatus) actions. | |
| if game.get("challenge") and action not in ['challengeResponse', 'getStatus', 'choose']: | |
| return {"success": False, "message": "A challenge is pending, only challenge responses are allowed."} | |
| if action == 'getStatus': | |
| check_game_status(game) | |
| return {"success": True, "challenge": game.get("challenge", None)} | |
| if action == 'steal': | |
| if game["turn"] != player: | |
| return {"success": False, "message": "Not your turn."} | |
| if game["permissions"].get(player, {}).get("steal", True): | |
| game["challenge"] = { | |
| "action": 'steal', | |
| "challenger": player, | |
| "target": target, | |
| "challengeType": 'steal', | |
| "status": 'pending' | |
| } | |
| return {"success": True, "message": f"Steal initiated by {player} targeting {target}. Awaiting response from {target}."} | |
| else: | |
| return {"success": False, "message": "You can only steal once per turn."} | |
| if action == 'endTurn': | |
| if game["turn"] != player: | |
| return {"success": False, "message": "Not your turn."} | |
| players_list = game.get("players", []) | |
| # Find the index of the current player in the players list | |
| current_index = next((i for i, p in enumerate(players_list) if p["name"] == player), None) | |
| if current_index is None: | |
| return {"success": False, "message": "Player not found in game."} | |
| next_index = (current_index + 1) % len(players_list) | |
| game["turn"] = players_list[next_index]["name"] | |
| game["permissions"][player]["gain"] = True | |
| game["permissions"][player]["steal"] = True | |
| return {"success": True, "message": f"Turn ended. Next turn: {game['turn']}"} | |
| if action == 'coup': | |
| if game["turn"] != player: | |
| return {"success": False, "message": "Not your turn."} | |
| player_data = next((p for p in game["players"] if p["name"] == player), None) | |
| if not player_data: | |
| return {"success": False, "message": "Player not found."} | |
| if player_data["coins"] < 7: | |
| return {"success": False, "message": "You don't have enough coins to coup another player."} | |
| player_data["coins"] -= 7 | |
| game["challenge"] = { | |
| "action": 'coup', | |
| "challenger": target, | |
| "target": player, | |
| "challengeType": 'coup', | |
| "status": 'choose' | |
| } | |
| return {"success": True, "message": f"Coup initiated by {player} targeting {target}."} | |
| if action == 'assassin': | |
| player_data = next((p for p in game["players"] if p["name"] == player), None) | |
| if player_data["coins"] < 3: | |
| return {"success": False, "message": "You don't have enough coins to assassinate another player."} | |
| if game["turn"] != player: | |
| return {"success": False, "message": "Not your turn."} | |
| game["challenge"] = { | |
| "action": "assassin", | |
| "challenger": player, | |
| "target": target, | |
| "challengeType": "assassin", | |
| "status": "pending", | |
| "phase": "target_decision" | |
| } | |
| player_data["coins"] -= 3 | |
| return {"success": True, "message": f"Assassin action initiated by {player} targeting {target}. Awaiting target's response."} | |
| if action == 'duke': | |
| if game["turn"] != player: | |
| return {"success": False, "message": "Not your turn."} | |
| # Duke is a money-earning action and only works if the player is allowed to gain. | |
| player_data = next((p for p in game["players"] if p["name"] == player), None) | |
| if not game["permissions"].get(player, {}).get("gain", True): | |
| return {"success": False, "message": "You can only earn money once per turn."} | |
| game["challenge"] = { | |
| "action": "duke", | |
| "challenger": player, | |
| "challengeType": "duke", | |
| "responses": {}, | |
| "status": "pending" | |
| } | |
| return {"success": True, "message": f"Duke action initiated by {player}. Waiting for opponents to respond."} | |
| if action == 'challengeResponse': | |
| if not game.get("challenge"): | |
| return {"success": False, "message": "No challenge pending."} | |
| # Handle Steal response. | |
| if game["challenge"]["challengeType"] == "steal": | |
| challenger_player = next(p for p in game["players"] if p["name"] == game["challenge"]["challenger"]) | |
| target_player = next(p for p in game["players"] if p["name"] == game["challenge"]["target"]) | |
| if response == 'accept': | |
| coins_to_steal = min(target_player["coins"], 2) | |
| target_player["coins"] -= coins_to_steal | |
| challenger_player["coins"] += coins_to_steal | |
| game["permissions"][challenger_player["name"]]["steal"] = False | |
| game["challenge"] = None | |
| return {"success": True, "message": f"Steal accepted. {coins_to_steal} coins transferred from {target_player['name']} to {challenger_player['name']}."} | |
| elif response == 'challenge': | |
| if "Captain" in challenger_player["cards"]: | |
| coins_to_steal = min(target_player["coins"], 2) | |
| target_player["coins"] -= coins_to_steal | |
| challenger_player["coins"] += coins_to_steal | |
| game["permissions"][challenger_player["name"]]["steal"] = False | |
| game["challenge"]["status"] = 'choose' | |
| game["challenge"]["challenger"] = target_player['name'] | |
| game["challenge"]["target"] = challenger_player['name'] | |
| return {"success": True, "message": f"Challenge failed. {target_player['name']} loses {coins_to_steal} coins to {challenger_player['name']}."} | |
| else: | |
| game["challenge"]["status"] = 'choose' | |
| return {"success": True, "message": f"Challenge successful. {challenger_player['name']} must choose a card to lose.", "challenge": game["challenge"]} | |
| # Handle Ambassador response. | |
| elif game["challenge"]["challengeType"] == "ambassador": | |
| if response == 'allow': | |
| if "responses" not in game["challenge"]: | |
| game["challenge"]["responses"] = {} | |
| game["challenge"]["responses"][player] = 'allow' | |
| total_opponents = len(game["players"]) - 1 | |
| if len(game["challenge"]["responses"]) == total_opponents: | |
| ambassador_player = next(p for p in game["players"] if p["name"] == game["challenge"]["challenger"]) | |
| ambassador_player["cards"] = generate_cards(len(ambassador_player["cards"])) | |
| game["challenge"] = None | |
| return {"success": True, "message": f"Card swap accepted. {ambassador_player['name']} swaps cards with the deck."} | |
| else: | |
| return {"success": True, "message": f"{player} allowed the card swap. Awaiting other responses."} | |
| elif response == 'challenge': | |
| ambassador_player = next(p for p in game["players"] if p["name"] == game["challenge"]["challenger"]) | |
| if "Ambassador" in ambassador_player["cards"]: | |
| game["challenge"]["status"] = "choose" | |
| game["challenge"]["challenger"] = player | |
| game["challenge"]["target"] = ambassador_player["name"] | |
| ambassador_player["cards"] = generate_cards(len(ambassador_player["cards"])) | |
| game["challenge"] = None | |
| return {"success": True, "message": f"Challenge failed. {player} must lose a card, while {ambassador_player['name']} swaps cards with the deck."} | |
| else: | |
| game["challenge"]["status"] = "choose" | |
| return {"success": True, "message": "Challenge successful. Ambassador user must choose a card to lose.", "challenge": game["challenge"]} | |
| # Handle Assassin/Contessa responses. | |
| elif game["challenge"]["challengeType"] == "assassin": | |
| if game["challenge"].get("phase") == "target_decision": | |
| if response == "allow": | |
| game["challenge"]["status"] = "choose" | |
| game["challenge"]["challenger"] = game["challenge"]["target"] | |
| return {"success": True, "message": f"{game['challenge']['target']} must now choose a card to lose."} | |
| elif response == "challenge": | |
| assassin_player = next(p for p in game["players"] if p["name"] == game["challenge"]["challenger"]) | |
| if "Assassin" in assassin_player["cards"]: | |
| target_player = next(p for p in game["players"] if p["name"] == game["challenge"]["target"]) | |
| target_player["cards"] = [] | |
| game["challenge"] = None | |
| return {"success": True, "message": f"Challenge failed. {target_player['name']} loses both cards."} | |
| else: | |
| game["challenge"]["status"] = "choose" | |
| game["challenge"]["challenger"] = assassin_player["name"] | |
| return {"success": True, "message": f"Challenge successful. {assassin_player['name']} must choose a card to lose.", "challenge": game["challenge"]} | |
| elif response == "contessa": | |
| game["challenge"]["phase"] = "assassin_contessa" | |
| return {"success": True, "message": f"{game['challenge']['challenger']} must now choose to challenge or accept the Contessa."} | |
| elif game["challenge"].get("phase") == "assassin_contessa": | |
| if response == "accept": | |
| game["challenge"]["status"] = "choose" | |
| game["challenge"]["challenger"] = game["challenge"]["target"] | |
| return {"success": True, "message": f"{game['challenge']['target']} must now choose a card to lose."} | |
| elif response == "challenge": | |
| target_player = next(p for p in game["players"] if p["name"] == game["challenge"]["target"]) | |
| if "Contessa" in target_player["cards"]: | |
| game["challenge"]["status"] = "choose" | |
| return {"success": True, "message": f"Contessa challenge successful. {game['challenge']['challenger']} must choose a card to lose.", "challenge": game["challenge"]} | |
| else: | |
| target_player["cards"] = [] | |
| game["challenge"] = None | |
| return {"success": True, "message": f"Contessa challenge failed. {target_player['name']} loses both cards."} | |
| # Handle Foreign Aid responses. | |
| elif game["challenge"]["challengeType"] == "foreignAid": | |
| challenger_player = next(p for p in game["players"] if p["name"] == game["challenge"]["challenger"]) | |
| # If the response is coming from an opponent (blocking the action) | |
| if player != game["challenge"]["challenger"]: | |
| if response == "block": | |
| # Instead of immediately ending the action, record the blocker and set status to pending block challenge. | |
| game["challenge"]["blocker"] = player | |
| game["challenge"]["status"] = "block_pending" | |
| return {"success": True, "message": f"{player} is attempting to block Foreign Aid. {game['challenge']['challenger']} may now challenge or allow the block.", "challenge": game["challenge"]} | |
| else: | |
| game["challenge"]["responses"][player] = response | |
| total_opponents = len(game["players"]) - 1 | |
| if len(game["challenge"]["responses"]) == total_opponents: | |
| challenger_player["coins"] += 2 | |
| game["permissions"][challenger_player["name"]]["gain"] = False | |
| game["challenge"] = None | |
| return {"success": True, "message": f"Foreign Aid accepted. {challenger_player['name']} gains 2 coins."} | |
| else: | |
| return {"success": True, "message": f"{player} allowed Foreign Aid. Awaiting other responses."} | |
| # Response from the challenger to a pending block. | |
| else: | |
| # This branch is for the foreign aid requester responding to a block. | |
| if response == "allow": | |
| game["challenge"] = None | |
| return {"success": True, "message": "Block accepted. Foreign Aid fails."} | |
| elif response == "challenge": | |
| blocker = game["challenge"].get("blocker") | |
| blocker_player = next(p for p in game["players"] if p["name"] == blocker) | |
| if "Duke" in blocker_player["cards"]: | |
| game["challenge"]["status"] = "choose" | |
| return {"success": True, "message": f"Challenge failed. {game['challenge']['challenger']} must choose a card to lose.", "challenge": game["challenge"]} | |
| else: | |
| challenger_player["coins"] += 2 | |
| game["permissions"][challenger_player["name"]]["gain"] = False | |
| game["challenge"]["challenger"] = blocker_player["name"] | |
| game["challenge"]["status"] = "choose" | |
| return {"success": True, "message": f"Challenge successful. {blocker} must choose a card to lose.", "challenge": game["challenge"]} | |
| # Handle Duke responses. | |
| elif game["challenge"]["challengeType"] == "duke": | |
| challenger_player = next(p for p in game["players"] if p["name"] == game["challenge"]["challenger"]) | |
| if player == game["challenge"]["challenger"]: | |
| # The player who initiated duke doesn't respond here. | |
| return {"success": False, "message": "Initiator cannot respond to their own Duke action."} | |
| if response == "allow": | |
| if "responses" not in game["challenge"]: | |
| game["challenge"]["responses"] = {} | |
| game["challenge"]["responses"][player] = "allow" | |
| total_opponents = len(game["players"]) - 1 | |
| if len(game["challenge"]["responses"]) == total_opponents: | |
| acting_player = next(p for p in game["players"] if p["name"] == game["challenge"]["challenger"]) | |
| acting_player["coins"] += 3 | |
| game["permissions"][challenger_player["name"]]["gain"] = False | |
| game["challenge"] = None | |
| return {"success": True, "message": f"Duke action accepted. {acting_player['name']} gains 3 coins."} | |
| else: | |
| return {"success": True, "message": f"{player} allowed the Duke action. Awaiting other responses."} | |
| elif response == "challenge": | |
| acting_player = next(p for p in game["players"] if p["name"] == game["challenge"]["challenger"]) | |
| if "Duke" in acting_player["cards"]: | |
| game["challenge"]["status"] = "choose" | |
| game["challenge"]["challenger"] = player | |
| game["challenge"]["target"] = acting_player["name"] | |
| acting_player["coins"] += 3 | |
| game["permissions"][challenger_player["name"]]["gain"] = False | |
| return {"success": True, "message": f"Challenge failed. {player} must choose a card to lose.", "challenge": game["challenge"]} | |
| else: | |
| game["challenge"]["status"] = "choose" | |
| return {"success": True, "message": f"Challenge successful. {acting_player['name']} must choose a card to lose.", "challenge": game["challenge"]} | |
| if action == 'ambassador': | |
| if game["turn"] != player: | |
| return {"success": False, "message": "Not your turn."} | |
| game["challenge"] = { | |
| "action": "ambassador", | |
| "challenger": player, | |
| "challengeType": "ambassador", | |
| "status": "pending", | |
| "responses": {} | |
| } | |
| return {"success": True, "message": f"Card swap initiated by {player}. Waiting for opponents to respond."} | |
| if action == 'choose': | |
| acting_player = next((p for p in game["players"] if p["name"] == game["challenge"]["challenger"]), None) | |
| if acting_player and target in acting_player["cards"]: | |
| acting_player["cards"].remove(target) | |
| game["challenge"] = None | |
| return {"success": True, "message": f"{acting_player['name']} loses the {target} card."} | |
| else: | |
| return {"success": False, "message": f"Card {target} not found in {acting_player['name']}'s hand."} | |
| if action == 'income': | |
| if game["turn"] != player: | |
| return {"success": False, "message": "Not your turn."} | |
| player_found = False | |
| for p in game["players"]: | |
| if p["name"] == player: | |
| player_found = True | |
| if not game["permissions"].get(player, {}).get("gain", True): | |
| return {"success": False, "message": "You can only earn money once per turn."} | |
| p["coins"] += 1 | |
| game["permissions"][player]["gain"] = False | |
| return {"success": True, "message": f"You now have {p['coins']} coins."} | |
| if not player_found: | |
| raise HTTPException(status_code=404, detail="Player not found in the game.") | |
| if action == 'foreignAid': | |
| if game["turn"] != player: | |
| return {"success": False, "message": "Not your turn."} | |
| if not game["permissions"].get(player, {}).get("gain", True): | |
| return {"success": False, "message": "You can only earn money once per turn."} | |
| # Initiate Foreign Aid but allow opponents to respond (block or allow). | |
| game["challenge"] = { | |
| "action": "foreignAid", | |
| "challenger": player, | |
| "challengeType": "foreignAid", | |
| "responses": {}, | |
| "status": "pending" | |
| } | |
| return {"success": True, "message": f"Foreign Aid initiated by {player}. Waiting for opponents to respond."} | |
| return {"success": True, "message": f"Action '{action}' processed for player {player}."} | |
| async def join_game(request: Request): | |
| data = await request.json() | |
| pin = data.get("pin") | |
| player_name = data.get("playerName") | |
| game = games.get(pin) | |
| if not game: | |
| return {"success": False, "message": "Game not found."} | |
| # Prevent joining if game has already started. | |
| if game.get("gameStarted"): | |
| return {"success": False, "message": "Cannot join a game that has started."} | |
| formatted_name = player_name | |
| existing_names = [name.lower() for name in game["permissions"].keys()] | |
| if formatted_name.lower() in existing_names: | |
| return {"success": False, "message": "Player name taken."} | |
| game["players"].append(formatted_name) | |
| game["permissions"][formatted_name] = {"steal": True, "gain": True} | |
| return {"success": True, "message": "You joined successfully!"} | |
| async def start_game(request: Request): | |
| data = await request.json() | |
| pin = data.get("pin") | |
| game = games.get(pin) | |
| if not game: | |
| raise HTTPException(status_code=404, detail="Game not found.") | |
| if game["gameStarted"]: | |
| raise HTTPException(status_code=400, detail="Game has already started.") | |
| if len(game["players"]) == 0: | |
| raise HTTPException(status_code=400, detail="No players in the game.") | |
| # Initialize players with 2 coins and cards. | |
| game["players"] = [{"name": name, "coins": 2, "cards": generate_cards(2)} for name in game["players"]] | |
| # Reset permissions to a dictionary with proper keys for each player. | |
| game["permissions"] = {player["name"]: {"steal": True, "gain": True} for player in game["players"]} | |
| game["gameStarted"] = True | |
| game["turn"] = game["players"][0]["name"] | |
| return {"success": True, "message": "Game started successfully!"} | |
| # New endpoint for deleting a game | |
| async def delete_game(request: Request): | |
| data = await request.json() | |
| pin = data.get("pin") | |
| newPin = data.get("newPin") | |
| if not pin: | |
| raise HTTPException(status_code=400, detail="PIN is required.") | |
| game = games.get(pin) | |
| if not game: | |
| raise HTTPException(status_code=404, detail="Game not found.") | |
| games[pin] = {"gameEnded": True, "newPin": newPin} | |
| return {"success": True, "message": "Game deleted successfully!"} | |
| def generate_cards(numofcards): | |
| cards = ['Duke', 'Assassin', 'Captain', 'Ambassador', 'Contessa'] | |
| shuffled = sorted(cards, key=lambda x: 0.5 - random.random()) | |
| if numofcards == 1: | |
| return [shuffled[0]] | |
| else: | |
| return [shuffled[0], shuffled[1]] | |
| if __name__ == "__main__": | |
| uvicorn.run(app, host="0.0.0.0", port=8000) | |
| def check_game_status(game): | |
| # Filter active players (only those who still have cards) | |
| active_players = [p for p in game.get("players", []) if p.get("cards")] | |
| # If the current turn player is no longer in the active players, update turn to the next available player's name. | |
| if not any(p["name"] == game.get("turn") for p in active_players): | |
| if active_players: | |
| game["turn"] = active_players[0]["name"] | |
| else: | |
| game["turn"] = None | |
| # Update the game players to only the active players. | |
| game["players"] = active_players | |
| # Update permissions: Flag players with more than 10 coins to coup. | |
| for p in game["players"]: | |
| name = p["name"] | |
| if p.get("coins", 0) > 10: | |
| if name not in game.get("permissions", {}): | |
| game.setdefault("permissions", {})[name] = {} | |
| game["permissions"][name]["mustCoup"] = True | |
| else: | |
| if name in game.get("permissions", {}): | |
| game["permissions"][name].pop("mustCoup", None) | |
| return game | |
| def capitalize_name(name): | |
| result = "" | |
| capitalize_next = True # Capitalize first letter | |
| for char in name: | |
| if capitalize_next and char.isalpha(): | |
| result += char.upper() | |
| capitalize_next = False | |
| else: | |
| result += char | |
| if char == " ": | |
| capitalize_next = True | |
| return result |