Coup_API / app.py
incognitolm's picture
Update app.py
c46a550 verified
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