תרגול מחלקות עם פתרון: חפיסת קלפים

התרגיל בחסות @orpazf

תרגול מחלקות: חפיסת קלפים

  1. כתבו מחלקה בשם Card המממשת קלף משחק שלו שתי תכונות: צורה ומספר.
    המחלקה תתמוך בפעולה __str__ ותאפשר להשוות בין שני קלפים לפי מספרם בלבד בעזרת סימני השוויון: < , <= , == , >= , > . השתמשו במתודות קסם.
  2. כעת, כתבו מחלקה בשם Deck שתכליתה אחסון של אוסף קלפים. אם היא אינה מקבלת חפיסת קלפים היא תיצור בעצמה חפיסת קלפים רגילה בת 52 קלפים, כאשר נסיך ייוצג כ־11, מלכה כ־12 ומלך כ־13.
    המחלקה תתמוך בפעולות הבאות (נסו להשתמש בכמה שיותר מתודות קסם):
    2.1 איחוד שתי חפיסות ליצירת חפיסה חדשה.
    2.2 בדיקה של האם קלף מסוים נמצא בחפיסה
    2.3 הפעולה len על חפיסה תחזיר את מספר הקלפים הנמצאים בה.
    2.4 טריפת החפיסה.
    2.5 שליפת קלף מהחפיסה – שליפת הקלף תוציא את הקלף מהחפיסה.
    2.6 פעולה שמקבלת כפרמטרים את מספר השחקנים ואת מספר הקלפים שיחולקו לכל שחקן. הפעולה תיצור רשימה של ידי שחקנים, כאשר יד של שחקן היא בפני עצמה רשימת הקלפים שחולקו לו.

פתרון מוקלט בחסות @orpazf יפורסם בשעה 21:00 כאן: תרגול מחלקות: חפיסת קלפים - YouTube

8 לייקים

אשמח להבהרה: Deck מקבלת רשימה של קלפים או n קלפים? רשום “חפיסת קלפים” אבל זה לא עוזר לי להבנה.

אני מימשתי עם רשימת קלפים :slight_smile:

לייק 1

מרגיש לי ש2.6 לא באמת לחלוטין אמורה להיות פעולה של המחלקה deck. לטעמי היא יותר שייכת למחלקה של משהו כמו game.

זו כמובן אפשרות. לדעתי מכיוון שחלוקת קלפים היא מחפיסה, ולא למשל מיד של שחקן או מסתם רשימת קלפים, היא מתאימה יותר להיות תחת Deck :slight_smile:

לכן לא אמרתי שהיא שייכת ליד של שחקן, אלא למשחק עצמו. פה אנחנו ממש מבצעים פעולות של משחק ולא פעולות של deck. מה שאנחנו מקבלים בחזרה הוא לא מאפיין של deck.

פתרון
import random


SUITS = {
    "clubs": "♣",
    "diamonds": "♦",
    "hearts": "♥",
    "spades": "♠",
    }


class Deck:
    def __init__(self, cards=None):
        if not cards:
            cards = self.create_deck_of_cards()
        self.cards = cards

    def __add__(self, other):
        return Deck(self.cards + other.cards)

    def __iter__(self):
        self.n = 0
        return self

    def __next__(self):
        if self.n >= len(self.cards):
            raise StopIteration
        else:
            self.n += 1
            return self.cards[self.n - 1]

    def __len__(self):
        return len(self.cards)

    def __repr__(self):
        return f'{self.cards}'

    def create_deck_of_cards(self):
        return [Card(value, suit) for suit in SUITS.values() for value in range(1, 14)]

    def shuffle(self):
        random.shuffle(self.cards)
        return True

    def draw_card(self):
        return self.cards.pop()

    def create_player_hands(self, number_of_players, number_of_cards_per_player):
        if len(self.cards) < number_of_players * number_of_cards_per_player:
            return False

        self.shuffle()
        player_hands = [
            [self.draw_card() for _ in range(number_of_cards_per_player)]
            for _ in range(number_of_players)
        ]
        return player_hands


class Card:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return f'{self.rank}{self.suit}'

    def __str__(self):
        return f'{self.rank}{self.suit}'

    def __eq__(self, other):
        return self.rank == other.rank

    def __ne__(self, other):
        return self.rank != other.rank

    def __lt__(self, other):
        return self.rank < other.rank

    def __gt__(self, other):
        return self.rank > other.rank

    def __le__(self, other):
        return self.rank <= other.rank

    def __ge__(self, other):
        return self.rank >= other.rank
לייק 1

נראה שהקוד שלך לא מתמודד יפה עם השורה d = Deck()

2 לייקים

תוקן…2020

ונראה לי שגם סעיף 2.2 לא מופיע אצלך

לא היה ממש צורך לעשות מימוש של זה מכיוון שזה עובד.

d2 = Deck([Card(8, "♣"), Card(4, "♣")])
card = Card(8, "♣")
print(card in d2)

מדפיס True

@orpazf תרגיל מצויין

פתרון
import random


class Card:
    def __init__(self, value, suit):
        self.value = value
        self.suit = suit.upper()

    def __str__(self):
        return f'{self.value}{self.suit}'

    def __lt__(self, other):
        return self.value < other.value

    def __le__(self, other):
        return self.value <= other.value

    def __eq__(self, other):
        return self.value == other.value

    def __ne__(self, other):
        return self.value != other.value

    def __gt__(self, other):
        return self.value > other.value

    def __ge__(self, other):
        return self.value >= other.value


class Deck:
    def __init__(self, *cards):
        if cards:
            self.cards = list(cards)
        else:
            self.cards = [Card(value, suit) for suit in ['c', 'd', 'h', 's'] for value in range(1, 14)]

    def __str__(self):
        return str([str(card) for card in self.cards])

    def __add__(self, other):
        return Deck(*(self.cards + other.cards))

    def __len__(self):
        return len(self.cards)

    def __contains__(self, other):
        for card in self.cards:
            if card == other and card.suit == other.suit:
                return True
        return False

    def shuffle(self):
        random.shuffle(self.cards)

    def sort(self, by_value=False):
        self.cards.sort()
        self.cards.sort(key=lambda card: card.suit)
        if by_value:
            self.cards.sort()

    def draw(self):
        self.shuffle()
        return self.cards.pop()

    def deal(self, players, amount):
        return [Deck(*[self.draw() for _ in range(amount)]) for _ in range(players)]
2 לייקים

למה ב-init של Deck לעשות [cards*] במקום list(cards)?
יש לי קצת יותר בעיה עם has_card:

  1. חפש מה אמור להיות השם של המתודה
  2. אתה בודק את המקרה שבו הכרטיס מיוצג כמחרוזת. זה בעייתי א’ כי זה לא כרטיס גם אם זה מודפס כאילו זה כן, ממש כמו שאם יבקשו שתבדוק האם 1 ברשימה לא תבדוק האם “1” נמצא בה. ב’ אם אתה בכל זאת רוצה לבדוק, שים לב שאם מישהו מגדיר חפיסת קלפים שבה הקלפים הם diamonds, hearts וכו’ הבדיקה הזו לא תתפוס עבורם ותזרוק שגיאה של ניסיון המרה ל-int :slight_smile:
פתרון

import random

SHAPES = {‘C’: ‘Club’, ‘D’: ‘Diamond’, ‘H’: ‘Heart’, ‘S’: ‘Spade’}
VALUES = [‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘10’, ‘11’, ‘12’, ‘13’]

class Card:
def init(self, shape, value):
self.shape = shape
self.value = value

def __str__(self):
    return f"{self.shape}{self.value}"

def __lt__(self, other):
    return self.value < other.value

def __gt__(self, other):
    return self.value > other.value

def __le__(self, other):
    return self.value <= other.value

def __ge__(self, other):
    return self.value >= other.value

def __cmp__(self, other):
    return self.value == other.value

class Deck:
def init(self, deck=None):
if deck is None:
deck = [Card(x,y) for x in SHAPES for y in VALUES]
self.deck = deck

def merge(self, other):
    self.deck.extend(other.deck)

def __contains__(self, item):
    return item in self.deck

def __len__(self):
    return len(self.deck)

def shuffle(self):
    random.shuffle(self.deck)

def drow_card(self):
    return self.deck.pop()

def __str__(self):
    return str([str(x) for x in self.deck])

בלי 2.6, כי אותו אממש כ-Game עם Deck ו-Hand

א. merge שלך לא יוצרת חפיסה חדשה
ב. לדעתי הפעולה שכינית merge אמורה להיות תחת מתודת קסם (חפש איזו)
ג. אם אתה מעדיף לא לממש את 2.6 כחלק מהמחלקה, אתה מוזמן לממש אותה כפונקציה חיצונית (מאמינה שגם במשחק אתה תפריד את הפעולה הזו משאר המשחק)
ד. שולי למדי אבל השתרבבה לך טעות כתיב, זה אמור להיות draw ולא drow :slight_smile:

הפתרון המוקלט של אורפז משודר כעת כאן, מוזמנים להצטרף לצפות.
עונים על שאלות בלייב – https://www.youtube.com/watch?v=-Sz62DeELKA

2 לייקים
  1. כי לפעמים אני לא חושב :upside_down_face:
    2.2. ההיגיון היה שניתן יהיה להעביר למתודה מחרוזת שמייצגת קלף ולא רק אובייקט של קלף ולבדוק אם הקלף שהמחרוזת מייצגת נמצא בחפיסה, אבל אכן לא חשבתי על מקרה בו הקלף יוגדר עם יותר מאות אחת, בכל מקרה עודכן.

בנוגע לפתרון שלך
בעקבות ההגדרה של __ eq__ שישווה רק את הערך המספרי של קלפים, נסיון לבדוק האם קלף בעל ערך מספרי קיים אך סמל “שונה” נמצא בחבילה יניב תוצאה שגויה. למשל: ‘13C’, ‘13 Clubs’ & ‘13akudgfkdzf’ כולם יחזירו True לבדיקה האם הם נמצאים בחבילה. זה לפחות מה שקרה לי עד שהוספתי השוואה גם של הסמל.

אתה צודק, זה אחד החסרונות בהגדרה השרירותית שקלפים מוגדרים רק לפי הערך המספרי שלהם. זה יהיה נכון למשל עבור המשחק מלחמה ופחות עבור משחק כמו ברידג’.
המטרה שעמדה לנגד עיניי היתה להראות עוד כמה מתודות קסם, כולל גם את <, > וכו’, והם פחות תופסים עבור השוואה בין סדרות שונות - אז התפשרתי :slight_smile:

מקבל את הפשרה. הייתי מגדיר אותה בתרגיל בכדי לחסוך כאב ראש :laughing:
אבל תכלס גם את זה פתרתי בסוף, אז זה אפילו יותר מספק.

לייק 1

אני בטוחה שכאב הראש רק לימד אותך :wink: