180 lines
5.0 KiB
Python
180 lines
5.0 KiB
Python
import random
|
|
from collections import defaultdict
|
|
from itertools import combinations
|
|
|
|
# Define card ranks and their order for runs
|
|
RANK_ORDER = {str(n): n for n in range(2, 11)}
|
|
RANK_ORDER.update({"A": 1, "J": 11, "Q": 12, "K": 13})
|
|
class Card:
|
|
suits = ['♠', '♥', '♦', '♣']
|
|
ranks = list(range(1, 11)) + ['J', 'Q', 'K']
|
|
|
|
def __init__(self, rank, suit):
|
|
self.rank = rank
|
|
self.suit = suit
|
|
|
|
@property
|
|
def value(self):
|
|
if isinstance(self.rank, int):
|
|
return self.rank
|
|
return 10 if self.rank in ['J', 'Q', 'K'] else 1 # Ace low
|
|
|
|
def __repr__(self):
|
|
return f"{self.rank}{self.suit}"
|
|
|
|
|
|
class Deck:
|
|
def __init__(self):
|
|
self.cards = [Card(rank, suit) for suit in Card.suits for rank in Card.ranks]
|
|
random.shuffle(self.cards)
|
|
|
|
def draw(self):
|
|
return self.cards.pop() if self.cards else None
|
|
|
|
|
|
class DiscardPile:
|
|
def __init__(self):
|
|
self.cards = []
|
|
|
|
def top(self):
|
|
return self.cards[-1] if self.cards else None
|
|
|
|
def discard(self, card):
|
|
self.cards.append(card)
|
|
|
|
|
|
|
|
class Hand:
|
|
def __init__(self):
|
|
self.cards = []
|
|
|
|
def add(self, card):
|
|
self.cards.append(card)
|
|
|
|
def remove(self, card):
|
|
self.cards.remove(card)
|
|
|
|
def get_all_melds(self):
|
|
"""Returns all valid sets and runs from current hand."""
|
|
return find_sets(self.cards) + find_runs(self.cards)
|
|
|
|
def best_meld_combo(self):
|
|
"""Returns the optimal non-overlapping meld combination."""
|
|
all_melds = self.get_all_melds()
|
|
combos = non_overlapping_meld_combos(all_melds)
|
|
best = max(combos, key=lambda combo: len(set(c for meld in combo for c in meld)), default=[])
|
|
return best
|
|
|
|
def deadwood(self):
|
|
"""Returns list of cards not used in best meld combo."""
|
|
used = set(c for meld in self.best_meld_combo() for c in meld)
|
|
return [c for c in self.cards if c not in used]
|
|
|
|
def deadwood_points(self):
|
|
return sum(c.value for c in self.deadwood())
|
|
|
|
def __repr__(self):
|
|
return f"Hand({self.cards})"
|
|
|
|
|
|
class Player:
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.hand = Hand()
|
|
|
|
def draw(self, deck, discard_pile, from_discard=False):
|
|
card = discard_pile.top() if from_discard else deck.draw()
|
|
if card:
|
|
self.hand.add(card)
|
|
if from_discard:
|
|
discard_pile.cards.pop()
|
|
return card
|
|
|
|
def discard(self, discard_pile, card):
|
|
self.hand.remove(card)
|
|
discard_pile.discard(card)
|
|
|
|
|
|
class Game:
|
|
def __init__(self, players):
|
|
self.deck = Deck()
|
|
self.discard_pile = DiscardPile()
|
|
self.players = players
|
|
|
|
def deal(self):
|
|
for _ in range(10):
|
|
for player in self.players:
|
|
player.hand.add(self.deck.draw())
|
|
self.discard_pile.discard(self.deck.draw())
|
|
|
|
def play_round(self):
|
|
# placeholder loop
|
|
self.deal()
|
|
print("Initial hands:")
|
|
for p in self.players:
|
|
print(p.name, p.hand)
|
|
|
|
|
|
# Function to detect sets
|
|
def find_sets(cards):
|
|
groups = defaultdict(list)
|
|
for c in cards:
|
|
groups[c.rank].append(c)
|
|
return [group for group in groups.values() if len(group) >= 3]
|
|
|
|
# Function to detect runs
|
|
def find_runs(cards):
|
|
runs = []
|
|
# group by suit
|
|
suits = defaultdict(list)
|
|
for c in cards:
|
|
suits[c.suit].append(c)
|
|
|
|
for suit, suited_cards in suits.items():
|
|
# sort by rank order
|
|
sorted_cards = sorted(suited_cards, key=lambda c: RANK_ORDER[str(c.rank)])
|
|
# scan for consecutive sequences
|
|
temp = [sorted_cards[0]]
|
|
for i in range(1, len(sorted_cards)):
|
|
prev = RANK_ORDER[str(sorted_cards[i-1].rank)]
|
|
curr = RANK_ORDER[str(sorted_cards[i].rank)]
|
|
if curr == prev + 1:
|
|
temp.append(sorted_cards[i])
|
|
else:
|
|
if len(temp) >= 3:
|
|
runs.append(temp)
|
|
temp = [sorted_cards[i]]
|
|
if len(temp) >= 3:
|
|
runs.append(temp)
|
|
return runs
|
|
|
|
def non_overlapping_meld_combos(all_melds):
|
|
"""
|
|
Given a list of all possible melds (sets and runs),
|
|
return all combinations where no card is used more than once.
|
|
"""
|
|
valid_combos = []
|
|
for r in range(1, len(all_melds) + 1):
|
|
for combo in combinations(all_melds, r):
|
|
used = set()
|
|
overlap = False
|
|
for meld in combo:
|
|
for card in meld:
|
|
if card in used:
|
|
overlap = True
|
|
break
|
|
used.add(card)
|
|
if overlap:
|
|
break
|
|
if not overlap:
|
|
valid_combos.append(combo)
|
|
return valid_combos
|
|
|
|
# start a game with two players
|
|
if __name__ == "__main__":
|
|
players = [Player("Alice"), Player("Bob")]
|
|
game = Game(players)
|
|
game.play_round()
|
|
# Further game logic would go here
|
|
|
|
|