|
|
|
import random |
|
import os |
|
import re |
|
|
|
# ANSI Color Codes |
|
COLORS = { |
|
"RESET": "\033[0m", |
|
"BLACK": "\033[30m", |
|
"RED": "\033[31m", |
|
"GREEN": "\033[32m", |
|
"YELLOW": "\033[33m", |
|
"BLUE": "\033[34m", |
|
"MAGENTA": "\033[35m", |
|
"CYAN": "\033[36m", |
|
"WHITE": "\033[37m", |
|
"BRIGHT_BLACK": "\033[90m", |
|
"BRIGHT_RED": "\033[91m", |
|
"BRIGHT_GREEN": "\033[92m", |
|
"BRIGHT_YELLOW": "\033[93m", |
|
"BRIGHT_BLUE": "\033[94m", |
|
"BRIGHT_MAGENTA": "\033[95m", |
|
"BRIGHT_CYAN": "\033[96m", |
|
"BRIGHT_WHITE": "\033[97m", |
|
} |
|
|
|
# Card constants |
|
SUITS = {"Hearts": "♥", "Diamonds": "♦", "Clubs": "♣", "Spades": "♠"} |
|
RANKS = {"2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "10": 10, "J": 11, "Q": 12, "K": 13, "A": 14} |
|
|
|
def create_deck(): |
|
"""Creates the initial Dungeon deck.""" |
|
deck = [] |
|
for suit_name, suit_symbol in SUITS.items(): |
|
for rank_name, rank_value in RANKS.items(): |
|
is_red = suit_name in ["Hearts", "Diamonds"] |
|
is_face_or_ace = rank_name in ["J", "Q", "K", "A"] |
|
|
|
if is_red and is_face_or_ace: |
|
continue |
|
|
|
card_type = "" |
|
if suit_name in ["Clubs", "Spades"]: |
|
card_type = "Monster" |
|
card_color = COLORS["RED"] |
|
elif suit_name == "Diamonds": |
|
card_type = "Weapon" |
|
card_color = COLORS["YELLOW"] |
|
elif suit_name == "Hearts": |
|
card_type = "Health Potion" |
|
card_color = COLORS["GREEN"] |
|
|
|
deck.append({ |
|
"name": f"{rank_name}{suit_symbol}", |
|
"suit": suit_name, |
|
"rank": rank_name, |
|
"value": rank_value, |
|
"type": card_type, |
|
"color": card_color |
|
}) |
|
return deck |
|
|
|
def clear_screen(): |
|
"""Clears the terminal screen.""" |
|
os.system('cls' if os.name == 'nt' else 'clear') |
|
|
|
def render_game_state(player_health, equipped_weapon, last_monster_value, room_cards, dungeon_deck_size, turns, message="", can_skip=False, choices_made=0): |
|
"""Renders the entire game interface.""" |
|
clear_screen() |
|
print("========================================") |
|
print(" SCOUNDREL") |
|
print("========================================") |
|
print(f" Health: {COLORS["GREEN"]}{player_health}/20{COLORS["RESET"]} Turn: {turns}") |
|
|
|
weapon_name = f"{COLORS["YELLOW"]}Barehanded{COLORS["RESET"]}" |
|
weapon_info = "" |
|
if equipped_weapon: |
|
weapon_name = f"{COLORS["YELLOW"]}{equipped_weapon['name']} ({equipped_weapon['value']}){COLORS["RESET"]}" |
|
if last_monster_value != float('inf'): |
|
weapon_info = f" (vs monsters < {last_monster_value})" |
|
else: |
|
weapon_info = " (newly equipped)" |
|
|
|
print(f" Weapon: {weapon_name}{weapon_info}") |
|
print(f" Deck: {dungeon_deck_size} cards remaining") |
|
print("----------------------------------------") |
|
|
|
if message: |
|
sentences = re.split(r'(?<=[.!?])\s+', message) |
|
for sentence in sentences: |
|
if sentence: # Avoid printing empty strings |
|
print(f" {sentence.strip()}") |
|
print("----------------------------------------") |
|
|
|
print(" Room:") |
|
if room_cards: |
|
for i, card in enumerate(room_cards): |
|
card_color = "" |
|
if card['type'] == 'Monster': |
|
card_color = COLORS["RED"] |
|
elif card['type'] == 'Weapon': |
|
card_color = COLORS["YELLOW"] |
|
elif card['type'] == 'Health Potion': |
|
card_color = COLORS["GREEN"] |
|
print(f" {i + 1}: {card_color}{card['name']}{COLORS["RESET"]} ({card['type']})") |
|
else: |
|
print(" The room is empty.") |
|
|
|
print("========================================") |
|
|
|
def game(): |
|
"""The main function for the Scoundrel game.""" |
|
clear_screen() |
|
print("Welcome to Scoundrel!") |
|
|
|
|
|
player_health = 20 |
|
dungeon_deck = create_deck() |
|
random.shuffle(dungeon_deck) |
|
|
|
equipped_weapon = None |
|
last_monster_value = float('inf') |
|
carry_over_card = None |
|
turns = 0 |
|
can_skip = True |
|
message = "" |
|
|
|
# The main game loop |
|
while player_health > 0 and (len(dungeon_deck) > 0 or carry_over_card): |
|
turns += 1 |
|
|
|
room_cards = [] |
|
if carry_over_card: |
|
room_cards.append(carry_over_card) |
|
carry_over_card = None |
|
|
|
while len(room_cards) < 4 and len(dungeon_deck) > 0: |
|
room_cards.append(dungeon_deck.pop(0)) |
|
|
|
if not room_cards: |
|
break |
|
|
|
potions_used_this_turn = 0 |
|
choices_made = 0 |
|
|
|
# Player's turn loop (3 choices) |
|
for i in range(3): |
|
if not room_cards or player_health <= 0: |
|
break |
|
|
|
render_game_state(player_health, equipped_weapon, last_monster_value, room_cards, len(dungeon_deck), turns, message, can_skip, choices_made) |
|
message = "" # Clear message after displaying it once |
|
|
|
# Get player choice |
|
valid_choices = [str(j + 1) for j in range(len(room_cards))] |
|
prompt = f"Choose a card (1-{len(room_cards)})" |
|
if can_skip and choices_made == 0: |
|
prompt += ", 's' to skip" |
|
prompt += " or 'q' to quit" |
|
valid_choices.append('q') |
|
if can_skip and choices_made == 0: |
|
valid_choices.append('s') |
|
|
|
player_input = "" |
|
while player_input not in valid_choices: |
|
player_input = input(f" {prompt}: ").lower() |
|
if player_input not in valid_choices: |
|
message = "Invalid choice. Please try again." |
|
render_game_state(player_health, equipped_weapon, last_monster_value, room_cards, len(dungeon_deck), turns, message, can_skip, choices_made) |
|
|
|
# Handle quitting the game |
|
if player_input == 'q': |
|
player_health = 0 # Set health to 0 to end the game loop |
|
message = "You quit the game." |
|
break |
|
|
|
|
|
# Handle skipping the room |
|
if player_input == 's': |
|
message = "You skipped the room. The cards are returned to the deck." |
|
dungeon_deck.extend(room_cards) |
|
random.shuffle(dungeon_deck) # Shuffle them back in |
|
room_cards = [] |
|
can_skip = False |
|
break |
|
|
|
choices_made += 1 |
|
choice = int(player_input) |
|
chosen_card = room_cards.pop(choice - 1) |
|
|
|
# --- Resolve the chosen card --- |
|
if chosen_card['type'] == 'Monster': |
|
damage = 0 |
|
if equipped_weapon and chosen_card['value'] < last_monster_value: |
|
damage = max(0, chosen_card['value'] - equipped_weapon['value']) |
|
message = f"You fought {COLORS["RED"]}{chosen_card['name']}{COLORS["RESET"]} with your {COLORS["YELLOW"]}{equipped_weapon['name']}{COLORS["RESET"]}. You took {COLORS["RED"]}{damage}{COLORS["RESET"]} damage." |
|
last_monster_value = chosen_card['value'] |
|
else: |
|
damage = chosen_card['value'] |
|
message = f"You fought {COLORS["RED"]}{chosen_card['name']}{COLORS["RESET"]} barehanded and took {COLORS["RED"]}{damage}{COLORS["RESET"]} damage." |
|
|
|
player_health -= damage |
|
|
|
elif chosen_card['type'] == 'Weapon': |
|
if equipped_weapon: |
|
message = f"You discarded your {COLORS["YELLOW"]}{equipped_weapon['name']}{COLORS["RESET"]}. " |
|
equipped_weapon = chosen_card |
|
last_monster_value = float('inf') |
|
message += f"You equipped the {COLORS["YELLOW"]}{equipped_weapon['name']}{COLORS["RESET"]}." |
|
|
|
elif chosen_card['type'] == 'Health Potion': |
|
if potions_used_this_turn == 0: |
|
heal_amount = chosen_card['value'] |
|
player_health = min(20, player_health + heal_amount) |
|
potions_used_this_turn += 1 |
|
message = f"You drank a potion and restored {COLORS["GREEN"]}{heal_amount}{COLORS["RESET"]} health." |
|
else: |
|
message = "You can only use one potion per room. It's discarded." |
|
|
|
if player_health <= 0: |
|
break |
|
|
|
# After the player's turn |
|
if player_input != 's': |
|
can_skip = True |
|
if room_cards: |
|
carry_over_card = room_cards[0] |
|
if message: # if there's a message from the last action, append to it |
|
message += f" {carry_over_card['name']} is carried to the next room." |
|
else: |
|
message = f"{carry_over_card['name']} is carried to the next room." |
|
|
|
# --- Game Over --- |
|
clear_screen() |
|
print("========================================") |
|
print(f"{COLORS["BRIGHT_RED"]} GAME OVER{COLORS["RESET"]}") |
|
print("========================================") |
|
if player_health <= 0: |
|
if message == "You quit the game.": |
|
print(f"{COLORS["YELLOW"]} You quit the game.{COLORS["RESET"]}") |
|
else: |
|
print(f"{COLORS["RED"]} You have been defeated!{COLORS["RESET"]}") |
|
else: |
|
print(f"{COLORS["GREEN"]} Congratulations! You cleared the dungeon!{COLORS["RESET"]}") |
|
print(f" Total Turns: {turns}") |
|
print("========================================") |
|
|
|
|
|
def show_intro_screen(): |
|
"""Displays the intro screen with ASCII art.""" |
|
clear_screen() |
|
print(f"{COLORS["RED"]} SCOUNDREL{COLORS["RESET"]}") |
|
print(f"{COLORS["WHITE"]}========================================={COLORS["RESET"]}") |
|
print(f"{COLORS["WHITE"]} A card game of dungeon crawling{COLORS["RESET"]}") |
|
print(f"{COLORS["WHITE"]}========================================={COLORS["RESET"]}") |
|
print(f"{COLORS["WHITE"]} by {COLORS["BLUE"]}Zach Gage{COLORS["WHITE"]} and {COLORS["BLUE"]}Kurt Bieg{COLORS["RESET"]}") |
|
print(f"{COLORS["WHITE"]} Game version by {COLORS["BLUE"]}Nick Gauthier{COLORS["RESET"]}") |
|
print(f"{COLORS["WHITE"]}========================================={COLORS["RESET"]}") |
|
# print(f"{COLORS["BRIGHT_WHITE"]} Press Enter to start...{COLORS["RESET"]}") |
|
input(f"{COLORS["BRIGHT_WHITE"]} Press Enter to start...{COLORS["RESET"]}") |
|
|
|
|
|
def show_rules_screen(): |
|
"""Displays the rules of the game.""" |
|
clear_screen() |
|
print(f"{COLORS["BRIGHT_CYAN"]}========================================{COLORS["RESET"]}") |
|
print(f"{COLORS["CYAN"]} RULES{COLORS["RESET"]}") |
|
print(f"{COLORS["BRIGHT_CYAN"]}========================================{COLORS["RESET"]}") |
|
print(f"{COLORS["WHITE"]}Goal: Clear the dungeon deck without dying.{COLORS["RESET"]}") |
|
print(f""" |
|
{COLORS["WHITE"]}How to Play:{COLORS["RESET"]}""") |
|
print(f"{COLORS["WHITE"]}1. Each turn, you get a new room of 4 cards.{COLORS["RESET"]}") |
|
print(f"{COLORS["WHITE"]}2. Choose 3 cards to play. The last one stays.{COLORS["RESET"]}") |
|
print(f""" |
|
{COLORS["WHITE"]}Card Types:{COLORS["RESET"]}""") |
|
print(f"{COLORS["RED"]}♠ Monsters: Deal damage equal to their value.{COLORS["RESET"]}") |
|
print(f"{COLORS["GREEN"]}♥ Potions: Heal you. Use one per room.{COLORS["RESET"]}") |
|
print(f"{COLORS["YELLOW"]}♦ Weapons: Fight monsters with less damage.{COLORS["RESET"]}") |
|
print(f""" |
|
{COLORS["WHITE"]}Important:{COLORS["RESET"]}""") |
|
print(f"{COLORS["WHITE"]}- You can skip a room, but not two in a row.{COLORS["RESET"]}") |
|
print(f"{COLORS["WHITE"]}- An equipped weapon can only be used on monsters with a strictly lower value than the one just defeated.{COLORS["RESET"]}") |
|
print(f"{COLORS["BRIGHT_CYAN"]}========================================{COLORS["RESET"]}") |
|
input(f"{COLORS["BRIGHT_WHITE"]} Press Enter to continue...{COLORS["RESET"]}") |
|
|
|
def main(): |
|
"""The main function for the Scoundrel game.""" |
|
# Temporarily bypass intro and rules for debugging |
|
show_intro_screen() |
|
show_rules_screen() |
|
while True: |
|
game() |
|
play_again = input("Play again? (y/n):").lower() |
|
if play_again != 'y': |
|
break |
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
main() |