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 = {} @app.post("/api/createGame") 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!"} @app.get("/api/test") async def test(): return {"success": True} @app.get("/api/getGames") async def getGames(): return {"success": True, "data": games} @app.get("/api/getGameStatus") 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} @app.post("/api/handleAction") 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}."} @app.post("/api/joinGame") 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!"} @app.put("/api/startGame") 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 @app.post("/api/deleteGame") 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