Created
May 9, 2023 00:04
-
-
Save huytd/57d45dd4de192ba7dbb78c7ab8844e62 to your computer and use it in GitHub Desktop.
Card game Thirteen (a.k.a Tiến lên miền nam) made by Bing Chat
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Step 1: Import the random module | |
import random | |
# Step 2: Define a class for cards | |
class Card: | |
def __init__(self, rank, suit): | |
self.rank = rank # an integer from 3 to 15 (3 is lowest and 15 is highest) | |
self.suit = suit.lower() # a string from "spades", "clubs", "diamonds", or "hearts" | |
def __str__(self): | |
# return a string representation of the card using symbols for suits | |
rank_dict = {11: "J", 12: "Q", 13: "K", 14: "A", 15: "2"} | |
suit_dict = {"spades": "♠", "clubs": "♣", "diamonds": "♦", "hearts": "♥"} | |
if self.rank in rank_dict: | |
rank_str = rank_dict[self.rank] | |
else: | |
rank_str = str(self.rank) | |
suit_str = suit_dict[self.suit] | |
return rank_str + suit_str | |
def __lt__(self, other): | |
# return True if this card is lower than the other card based on rank and suit | |
if self.rank < other.rank: | |
return True | |
elif self.rank == other.rank: | |
if self.suit == "spades": | |
return True | |
elif self.suit == "clubs" and other.suit != "spades": | |
return True | |
elif self.suit == "diamonds" and other.suit in ["spades", "clubs"]: | |
return True | |
elif self.suit == "hearts" and other.suit == "hearts": | |
return True | |
else: | |
return False | |
else: | |
return False | |
# Step 3: Define a class for decks | |
class Deck: | |
def __init__(self): | |
self.cards = [] | |
# create 52 cards and add them to the deck | |
for rank in range(3, 16): | |
for suit in ["spades", "clubs", "diamonds", "hearts"]: | |
card = Card(rank, suit) | |
self.cards.append(card) | |
def shuffle(self): | |
# shuffle the deck using the random module | |
random.shuffle(self.cards) | |
def deal(self): | |
# deal one card from the top of the deck and return it | |
return self.cards.pop() | |
# Step 4: Define a class for hands | |
class Hand: | |
def __init__(self): | |
self.cards = [] | |
def add_card(self, card): | |
# add a card to the hand | |
self.cards.append(card) | |
def remove_cards(self, cards): | |
# remove a list of cards from the hand | |
for card in cards: | |
self.cards.remove(card) | |
def sort(self): | |
# sort the hand by rank and suit using the built-in sorted function | |
self.cards = sorted(self.cards) | |
def display(self): | |
# display the hand as a string of cards separated by spaces | |
return " ".join([str(card) for card in self.cards]) | |
# Step 5: Define a class for players | |
class Player: | |
def __init__(self, name): | |
self.name = name # a string for the player's name | |
self.hand = Hand() # a hand object for the player's cards | |
def draw_card(self, deck): | |
# draw one card from the deck and add it to the hand | |
card = deck.deal() | |
self.hand.add_card(card) | |
def play_cards(self, cards): | |
# play a list of cards from the hand and return them | |
self.hand.remove_cards(cards) | |
return cards | |
def has_card(self, rank, suit): | |
# check if the player has a specific card in their hand | |
for card in self.hand.cards: | |
if card.rank == rank and card.suit == suit: | |
return True | |
return False | |
def is_empty(self): | |
# check if the player has no cards left in their hand | |
return len(self.hand.cards) == 0 | |
# Step 6: Define a function for comparing two combinations of cards | |
def compare_combos(combo1, combo2): | |
# compare two combinations of cards based on their type and rank | |
# return 1 if combo1 is higher than combo2, -1 if combo2 is higher than combo1, or 0 if they are equal or invalid | |
# get the type and rank of each combination using helper functions (defined later) | |
type1, rank1 = get_combo_type_and_rank(combo1) | |
type2, rank2 = get_combo_type_and_rank(combo2) | |
# compare the types first | |
if type1 > type2: | |
return 1 | |
elif type1 < type2: | |
return -1 | |
else: | |
# if the types are equal, compare the ranks | |
if rank1 > rank2: | |
return 1 | |
elif rank1 < rank2: | |
return -1 | |
else: | |
return 0 | |
# Step 7: Define a function for checking if a combination is valid based on the rules of the game | |
def is_valid_combo(combo, prev_combo=None): | |
# check if a combination of cards is valid based on the rules of the game | |
# optionally, check if it can beat the previous combination played | |
# get the type and rank of the combination using helper functions (defined later) | |
type, rank = get_combo_type_and_rank(combo) | |
# if the type is 0, it means the combination is invalid | |
if type == 0: | |
return False | |
# if there is no previous combination, any valid combination can be played | |
if prev_combo is None: | |
return True | |
# otherwise, compare the combination with the previous one using the compare_combos function (defined earlier) | |
result = compare_combos(combo, prev_combo) | |
# if the result is 1, it means the combination can beat the previous one | |
if result == 1: | |
return True | |
# otherwise, it cannot beat the previous one or it is equal or invalid | |
else: | |
return False | |
# Step 8: Define a function for getting user input for playing a combination or passing | |
def get_user_input(player, prev_combo=None): | |
# get user input for playing a combination or passing | |
# return a list of cards if the user plays a valid combination | |
# return an empty list if the user passes or enters an invalid input | |
# display the player's hand and the previous combination (if any) | |
print(f"{player.name}, your hand is: {player.hand.display()}") | |
if prev_combo is not None: | |
print(f"The previous combination is: {display_combo(prev_combo)}") | |
# loop until the user enters a valid input | |
while True: | |
# prompt the user to enter a combination or pass | |
user_input = input("Enter a combination of cards or pass: ").strip().upper() | |
# if the user enters "pass", return an empty list | |
if user_input == "PASS": | |
return [] | |
# otherwise, try to parse the user input into a list of cards | |
try: | |
cards = [] | |
splitted = user_input.split() | |
for i in range(0, len(splitted), 2): | |
rank = splitted[i] | |
suit = splitted[i + 1] | |
card = Card(int(rank), suit) | |
cards.append(card) | |
print("INPUT") | |
print(*cards, sep=", ") | |
print("PLAYER's HAND") | |
print(*player.hand.cards, sep=", ") | |
# check if the cards are in the player's hand | |
for card in cards: | |
if not player.has_card(card.rank, card.suit): | |
raise ValueError("You don't have that card in your hand.") | |
# check if the combination is valid and can beat the previous one (if any) | |
if is_valid_combo(cards, prev_combo): | |
# return the list of cards | |
return cards | |
else: | |
raise ValueError("That is not a valid combination.") | |
# if there is any error in parsing or validating the user input, display an error message and repeat the loop | |
except ValueError as e: | |
print(f"Error: {e}") | |
print("Please enter a valid combination or pass.") | |
# Step 9: Define a function for displaying the current state of the game | |
def display_state(players): | |
# display the current state of the game | |
# show how many cards each player has left and who is the current leader | |
# create a list of tuples containing the player's name and number of cards left | |
stats = [(player.name, len(player.hand.cards)) for player in players] | |
# sort the list by number of cards in ascending order | |
stats.sort(key=lambda x: x[1]) | |
# print the list as a table with headers | |
print("Name\tCards") | |
print("----\t-----") | |
for name, cards in stats: | |
print(f"{name}\t{cards}") | |
# print who is the current leader based on the first element of the list | |
leader_name, leader_cards = stats[0] | |
print(f"The current leader is {leader_name} with {leader_cards} cards left.") | |
# Step 10: Define a function for simulating the computer's move based on some simple strategy | |
def get_computer_move(player, prev_combo=None): | |
# simulate the computer's move based on some simple strategy | |
# return a list of cards if the computer plays a valid combination | |
# return an empty list if the computer passes | |
# sort the player's hand by rank and suit | |
player.hand.sort() | |
# if there is no previous combination, play the lowest single card or the 3 of spades (if the player has it) | |
if prev_combo is None: | |
if player.has_card(3, "spades"): | |
return [Card(3, "spades")] | |
else: | |
return [player.hand.cards[0]] | |
# otherwise, try to find the lowest combination that can beat the previous one | |
else: | |
# get the type and rank of the previous combination using helper functions (defined later) | |
prev_type, prev_rank = get_combo_type_and_rank(prev_combo) | |
# loop through all possible combinations of cards in the player's hand that have the same type as the previous one | |
for i in range(len(player.hand.cards) - prev_type + 1): | |
combo = player.hand.cards[i:i+prev_type] | |
# check if the combination is valid and can beat the previous one using helper functions (defined earlier) | |
if is_valid_combo(combo, prev_combo): | |
# return the combination | |
return combo | |
# if no such combination is found, pass by returning an empty list | |
return [] | |
def get_combo_type_and_rank(combo): | |
n = len(combo) | |
if n == 0: | |
return (0, 0) | |
elif n == 1: | |
rank = combo[0].rank | |
return (1, rank) | |
elif n == 2: | |
if combo[0].rank == combo[1].rank: | |
rank = combo[0].rank | |
return (2, rank) | |
else: | |
return (0, 0) | |
elif n == 3: | |
if combo[0].rank == combo[1].rank == combo[2].rank: | |
rank = combo[0].rank | |
return (3, rank) | |
else: | |
return (0, 0) | |
elif n == 4: | |
if combo[0].rank == combo[1].rank == combo[2].rank == combo[3].rank: | |
rank = combo[0].rank | |
return (4, rank) | |
else: | |
return (0, 0) | |
else: | |
combo = sorted(combo) | |
ranks = [] | |
suits = [] | |
for card in combo: | |
ranks.append(card.rank) | |
suits.append(card.suit) | |
if len(set(suits)) == 1 and ranks == list(range(min(ranks), max(ranks) + 1)): | |
highest_rank = max(ranks) | |
return (5, highest_rank) | |
else: | |
for i in range(1, n - 2): | |
if suits[i] != suits[i + 1]: | |
ranks1 = ranks[:i + 1] | |
suits1 = suits[:i + 1] | |
ranks2 = ranks[i + 1:] | |
suits2 = suits[i + 1:] | |
if len(set(suits1)) == len(set(suits2)) == 1 and ranks1 == list(range(min(ranks1), max(ranks1) + 1)) and ranks2 == list(range(min(ranks2), max(ranks2) + 1)): | |
highest_rank = max(max(ranks1), max(ranks2)) | |
return (6, highest_rank) | |
return (0, 0) | |
def display_combo(combo): | |
return " ".join([str(card) for card in combo]) | |
def main(): | |
human = Player(input("Enter your name: ")) | |
computer1 = Player("Alice") | |
computer2 = Player("Bob") | |
computer3 = Player("Charlie") | |
players = [human, computer1, computer2, computer3] | |
random.shuffle(players) | |
deck = Deck() | |
deck.shuffle() | |
for i in range(13): | |
for player in players: | |
player.draw_card(deck) | |
for player in players: | |
player.hand.sort() | |
for i, player in enumerate(players): | |
if player.has_card(3, "spades"): | |
players = players[i:] + players[:i] | |
break | |
prev_combo = None | |
while True: | |
for player in players: | |
display_state(players) | |
if player == human: | |
combo = get_user_input(player, prev_combo) | |
else: | |
combo = get_computer_move(player, prev_combo) | |
if len(combo) == 0: | |
print(f"{player.name} passes.") | |
else: | |
print(f"{player.name} plays: {display_combo(combo)}") | |
prev_combo = combo | |
if player.is_empty(): | |
print(f"{player.name} has no cards left.") | |
if player == human: | |
print("You win!") | |
else: | |
print("You lose!") | |
return | |
print() | |
# run the main function | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment