Referencja: Allen B. Downey, Jeffrey Elkner and Chris Meyers, How to Think Like a Computer Scientist [różne wersje dostępne w sieci].
Gra Old Maid (odmiana gry Czarny Piotruś) jest przykładem zastosowania dziedziczenia. Celem gry jest pozbycie się wszystkich kart z ręki. Karty odkładamy w parach, a parę tworzą dwie czarne (Trefl, Pik) lub dwie czerwone (Karo, Kier) karty o tym samym numerze. Gracze w koło ciągną od siebie karty i odkładają pary aż zostanie jeden gracz z jedną kartą bez pary (na początku gry usuwa się z talii królową trefl). W poniższym programie komputer symuluje grę wszystkich graczy.
# Implementacja w Pythonie 2.7 (__cmp__).
class Card:
"""Klasa reprezentująca karty do gry."""
# Te listy poniżej są widoczne w metodach tej klasy.
# Numery i kolory kart są zakodowane (mapowane) w dwóch listach.
suit_list = ["trefl", "karo", "kier", "pik"]
rank_list = ["puste","As","2","3","4","5","6","7","8","9","10","Walet","Dama","Krol"]
def __init__(self, suit=0, rank=0):
"""Konstruktor karty."""
self.suit = suit
self.rank = rank
def __str__(self):
"""Zwróć postać napisową karty."""
# siódemka kier ma postać "7 Kier"
return "{} {}".format(Card.rank_list[self.rank], Card.suit_list[self.suit])
def __repr__(self):
"""Zwróć reprezentację napisową."""
# siódemka kier ma postać "Card(2, 7)"
return "Card({}, {})".format(self.suit, self.rank)
def __cmp__(self, other):
"""Porównaj karty."""
# Sprawdzamy kolory kart.
if self.suit > other.suit:
return 1
if self.suit < other.suit:
return -1
# Kolory te same, sprawdzamy numery.
if self.rank > other.rank:
return 1
if self.rank < other.rank:
return -1
return 0 # remis
# Krótka wersja (Py2).
#return cmp((self.suit, self.rank), (other.suit, other.rank))
class Deck:
"""Klasa reprezentująca talię."""
def __init__(self):
"""Stwórz całą talię kart."""
self.cards = []
for suit in range(4):
for rank in range(1, 14):
self.cards.append(Card(suit, rank))
def __str__(self):
"""Zwróć postać napisową talii kart."""
s = ""
for i in range(len(self.cards)):
s = s + " " * i + str(self.cards[i]) + "\n"
return s
def shuffle(self):
"""Tasuj talię."""
import random
n_cards = len(self.cards)
for i in range(n_cards):
j = random.randrange(i, n_cards)
self.cards[i], self.cards[j] = self.cards[j], self.cards[i]
def remove_card(self, card):
"""Unicestwianie karty."""
if card in self.cards:
self.cards.remove(card)
return True
else:
return False
def pop_card(self):
"""Rozdanie (wydanie na zewnatrz) jednej karty."""
return self.cards.pop() # pop() usuwa ostatni element z listy
def is_empty(self):
"""Test czy talia jest pusta."""
return self.cards == []
def deal(self, hands, n_cards=999):
"""Rozdaj karty do rąk."""
# Domyślnie rozdaje wszystkie karty do dostępnych rąk.
# hands to lista lub tuple rąk.
n_hands = len(hands)
for i in range(n_cards):
if self.is_empty():
break
card = self.pop_card() # weź kartę
hand = hands[i % n_hands] # do której reki
hand.add_card(card) # dodaj kartę
class Hand(Deck): # DZIEDZICZENIE z klasy Deck
"""Klasa reprezentująca rękę gracza."""
# Ręka to odmiana talii, każda metoda talii działa także dla ręki.
def __init__(self, name=""):
"""Konstruktor ręki."""
self.cards = []
self.name = name # nazwa ręki, domyślnie pusta
def __str__(self):
"""Zwróć postać napisową ręki."""
# To nadpisze metodę __str__ odziedziczoną z Deck.
s = "Ręka " + self.name
if self.is_empty():
s = s + " jest pusta\n"
else:
s = s + " zawiera\n"
# Korzystamy z przestrzeni nazw Deck.
return s + Deck.__str__(self)
def add_card(self, card):
"""Dodanie karty do ręki."""
self.cards.append(card)
class CardGame:
"""Ogólna klasa dla gier karcianych."""
# W tej klasie chcemy mieć wspólne cechy różnych gier.
def __init__(self):
"""Konstruktor gry karcianej."""
# Mamy tu nie tylko inicjalizację atrybutu, ale konkretne obliczenia.
# Ogólnie gra ma dwa atrybuty: deck, hands.
self.deck = Deck()
self.deck.shuffle() # tasowanie talii
self.hands = []
def print_hands(self):
"""Wypisz ręce graczy."""
for hand in self.hands:
print(hand)
class OldMaidHand(Hand): # tworzymy rękę do naszej gry
# Metoda __init__ jest dziedziczona z klasy Hand.
def remove_matches(self):
count = 0
# Kopiujemy karty, bo Python może zgłupieć, gdy pętla jest po liście,
# która się zmienia (usuwamy karty).
original_cards = self.cards[:]
for card in original_cards:
match = Card(3 - card.suit, card.rank)
if match in self.cards:
self.cards.remove(card)
self.cards.remove(match)
print("Ręka {}: {} tworzy parę z {}".format(self.name, card, match))
count += 1
return count
class OldMaidGame(CardGame): # tworzymy naszą grę
def play(self, names):
# Usuwamy królową trefl.
self.deck.remove_card(Card(0, 12))
# Tworzymy rekę każdego gracza jako atrybuty gry.
for name in names:
self.hands.append(OldMaidHand(name))
# Rozdajemy karty do rąk.
self.deck.deal(self.hands)
print("---- Rozdano karty")
self.print_hands()
# Usuwamy początkowe pary.
matches = self.remove_all_matches()
print("---- Pary usunięte, początek gry")
self.print_hands()
# Gramy dopóki nie będzie sparowanych 50 kart.
turn = 0
n_hands = len(self.hands)
while matches < 25: # 25 par to 50 kart
matches += self.play_one_turn(turn)
turn = (turn + 1) % n_hands
print("---- Koniec gry")
self.print_hands()
def remove_all_matches(self):
count = 0
for hand in self.hands:
count += hand.remove_matches()
return count
def play_one_turn(self, i):
if self.hands[i].is_empty():
return 0
neighbor = self.find_neighbor(i)
picked_card = self.hands[neighbor].pop_card()
self.hands[i].add_card(picked_card)
print("Ręka {} pobiera {}".format(self.hands[i].name, picked_card))
count = self.hands[i].remove_matches()
self.hands[i].shuffle()
return count
def find_neighbor(self, i):
n_hands = len(self.hands)
for next in range(1, n_hands):
neighbor = (i + next) % n_hands
if not self.hands[neighbor].is_empty():
return neighbor
game = OldMaidGame() game.play(["Adam", "Bogdan", "Cezary"])