class Animal:
    pass


class Mammal(Animal):
    pass


class Bat(Mammal):
    pass


class Rabbit(Mammal):
    pass

"""
Explanation: <img src="images/logo.jpg" style="display: block; margin-left: auto; margin-right: auto;" alt="לוגו של מיזם לימוד הפייתון. נחש מצויר בצבעי צהוב וכחול, הנע בין האותיות של שם הקורס: לומדים פייתון. הסלוגן המופיע מעל לשם הקורס הוא מיזם חינמי ללימוד תכנות בעברית.">
<span style="text-align: right; direction: rtl; float: right;">ירושה – חלק 2</span>
<span style="text-align: right; direction: rtl; float: right; clear: both;">הקדמה</span>
<p style="text-align: right; direction: rtl; float: right; clear: both;">
    בשיעור הקודם למדנו על <dfn>ירושה</dfn> – מנגנון תכנותי שמאפשר יצירת מחלקה על בסיס תכונותיה ופעולותיה של מחלקה אחרת.<br>
    סקרנו באילו מקרים נכון לבצע ירושה, ודיברנו על חילוקי הדעות ועל הסיבוכים האפשריים שירושה עלולה לגרום. 
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    חקרנו את היכולת של תת־מחלקה להגדיר מחדש פעולות של מחלקת־העל וקראנו לכך "דריסה" של פעולה.<br>
    דיברנו גם על הפונקציה <var>super</var> שמאפשרת לנו לקרוא בקלות לפעולות במחלקת־העל.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    ירושה היא כר פורה לשיח בין תיאורטיקנים של מדעי המחשב.<br>
    נכתבו עליה מילים רבות, והיא נושא מרכזי בדיונים על הנדסת תוכנה.<br>
    במחברת זו נעמיק ונסקור שימושים וטכניקות שהתפתחו מתוך רעיון הירושה במחלקות.
</p>

<span style="text-align: right; direction: rtl; float: right; clear: both;">שימושים נפוצים לירושה</span>
<span style="text-align: right; direction: rtl; float: right; clear: both;">רמות ירושה מרובות</span>
<p style="text-align: right; direction: rtl; float: right; clear: both;">
    הרעיון הבסיסי ביותר בירושה הוא העובדה שיכולה להיות יותר מרמה אחת של ירושה.<br>
    קרי: אם מחלקה ב ירשה ממחלקה א, מחלקה ג יכולה לרשת ממחלקה ב.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    בקטע הקוד הבא, לדוגמה, המחלקה יונק (<var>Mammal</var>) יורשת מהמחלקה "חיה" (<var>Animal</var>).<br>
    המחלקות עטלף (<var>Bat</var>) וארנב (<var>Rabbit</var>) יורשות מהמחלקה "יונק".
</p>
End of explanation
"""


Bat.mro()

"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    במקרה כזה, המחלקות <var>Bat</var> ו־<var>Rabbit</var> ירשו הן את התכונות והפעולות של המחלקה <var>Mammal</var>, והן את אלו של <var>Animal</var>.
</p>

<figure>
    <img src="images/multilevel_inheritance.svg?v=1" style="max-width: 400px; margin-right: auto; margin-left: auto; text-align: center;" alt="בתמונה יש שתי תיבות. השמאלית עם הכותרת Song, ובה התכונות והפעולות השייכות למחלקה. אליה יוצא חץ מהתיבה הימנית, עם הכותרת Acrostic, ובה מופיעות אותן תכונות ופעולות בצבע אפור (כדי להמחיש שהן נלקחות מתוך המחלקה Song)."/>
    <figcaption style="margin-top: 2rem; text-align: center; direction: rtl;">דוגמה לרמות ירושה מרובות: המחלקות <var>Rabbit</var> ו־<var>Bat</var> יורשות ממחלקה שיורשת ממחלקה נוספת.
    </figcaption>
</figure>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    אפשר לקבל את שרשרת מחלקות־העל של מחלקה מסוימת לפי סדרן בעזרת <code dir="ltr">class_name.mro()</code>:
</p>
End of explanation
"""


class Dog:
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def make_sound(self):
        print("Woof")
    
    def __str__(self):
        return f"I'm {self.name} the dog!"


class Cow:
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def make_sound(self):
        print("Muuuuuuuuuu")

    def __str__(self):
        return f"I'm {self.name} the cow!"


class Dove:
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def make_sound(self):
        print("Kukukuku!")

    def __str__(self):
        return f"I'm {self.name} the dove!"


print(Dove("Rexi", "Female"))

"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    בפנייה לפעולה כלשהי, תחפש פייתון את הפעולה במחלקה הראשונה שמופיעה ב־MRO.<br>
    אם הפעולה לא מופיעה שם, תיגש פייתון למחלקה שאחריה, כך עד שהיא תגיע ל־<var>object</var> שתמיד יהיה בראש השרשרת.<br>
    אם הפעולה לא קיימת באף אחת מהמחלקות שמופיעות ב־MRO, פייתון תזרוק <var>NameError</var>.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    מבחינה הנדסית, מומלץ להימנע ככל האפשר מירושה מרובת רמות כשאין בכך צורך ממשי.<br>
    ירושה מרובת רמות תגדיל את הסיכוי לתסמונת מחלקת־העל השברירית, תקשה על בדיקת התוכנית ותיצור בעיות תחזוקה בעתיד.
</p>

<span style="text-align: right; direction: rtl; float: right; clear: both;">מחלקה מופשטת</span>
<p style="text-align: right; direction: rtl; float: right; clear: both;">
    כל חיה משמיעה צליל האופייני לה: כלב נובח, פרה גועה ויונה הומה.<br>
    נממש מחלקה עבור כל חיה:
</p>
End of explanation
"""


class Animal:
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender


class Dog(Animal):
    def make_sound(self):
        print("Woof")

    def __str__(self):
        return f"I'm {self.name} the dog!"


class Cow(Animal):
    def make_sound(self):
        print("Muuuuuuuuuu")

    def __str__(self):
        return f"I'm {self.name} the cow!"


class Dove(Animal):
    def make_sound(self):
        print("Kukukuku!")

    def __str__(self):
        return f"I'm {self.name} the dove!"


print(Dove("Rexi", "Female"))

"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    נשים לב שצורת כל המחלקות דומה – מקרה קלאסי לירושה:
</p>
End of explanation
"""


class Animal:
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
    
    def make_sound(self):
        pass

    def __str__(self):
        pass


class Dog(Animal):
    def make_sound(self):
        print("Woof")

    def __str__(self):
        return f"I'm {self.name} the dog!"


class Cow(Animal):
    def make_sound(self):
        print("Muuuuuuuuuu")

    def __str__(self):
        return f"I'm {self.name} the cow!"


class Dove(Animal):
    def make_sound(self):
        print("Kukukuku!")

    def __str__(self):
        return f"I'm {self.name} the dove!"


print(Dove("Rexi", "Female"))

"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    לעיתים קרובות אנחנו רוצים לממש קבוצת מחלקות שיש להן את אותן תכונות ופעולות – בדיוק כמו במקרה של <var>Dog</var>, <var>Cow</var> ו־<var>Dove</var>.<br>
    במקרה כזה נפנה באופן טבעי לירושה, שבה מחלקת־העל תכיל את התכונות והפעולות המשותפות לכל המחלקות.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    החיסרון במחלקת <var>Animal</var> הוא שכעת אפשר ליצור ישירות ממנה מופעים.<br>
    זה לא מה שהתכוונו שיקרה. המחלקה הזו קיימת רק כדי לייצג רעיון מופשט של חיה, ולאפשר ירושת תכונות ופעולות ממקור אחד.<br>
    מטרת התוכנית היא לאפשר לבעלי חיים להשמיע קול, ולכן אין משמעות ביצירת מופע מהמחלקה <var>Animal</var> – שהרי ל"חיה" כרעיון מופשט אין קול.<br>
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    חיסרון נוסף הוא שמחלקות יכולות לרשת ממחלקת <var>Animal</var> מבלי לממש את הפעולה <var>make_sound</var>.<br>
    אם היינו מתכנתים פיסת קוד כללית שמטפלת בחיות, ייתכן שזה היה תקין, אבל לא זה המקרה בקוד שלמעלה.<br>
    מטרת התוכנה שלנו הייתה מלכתחילה לייצג קולות של חיות, ומחלקה שיורשת מ־<var>Animal</var> ולא מממשת את <var>make_sound</var> היא פתח לבאגים בעתיד.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    הפתרון לשתי הבעיות שהוצגו כאן נקרא בהנדסת תוכנה "<dfn>מחלקה מופשטת</dfn>" (<dfn>Abstract Class</dfn>).<br> 
    זהו מצב שבו אנחנו משתמשים במחלקת־על ל־3 צרכים:
</p>

<ol style="text-align: right; direction: rtl; float: right; clear: both;">
    <li>איגוד של תתי־מחלקות והורשת תכונות ופעולות לכולן (ירושה קלאסית).</li>
    <li>אכיפה שכל תתי־המחלקות יממשו פעולות שהגדרנו במחלקת־העל כפעולות שחייבים לממש.</li>
    <li>אכיפה שתאסור יצירת מופעים ממחלקת־העל המופשטת. יצירת מופעים תתאפשר רק מתתי־המחלקות שיורשות אותה.</li>
</ol>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    נהוג לכתוב במחלקת־העל המופשטת את כותרות הפעולות שעל תת־המחלקות שיורשות ממנה לממש:
</p>
End of explanation
"""


from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    @abstractmethod
    def make_sound(self):
        pass

    @abstractmethod
    def __str__(self):
        pass


class Dog(Animal):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def make_sound(self):
        print("Woof")

    def __str__(self):
        return f"I'm {self.name} the dog!"


class Cow(Animal):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def make_sound(self):
        print("Muuuuuuuuuu")

    def __str__(self):
        return f"I'm {self.name} the cow!"


class Dove(Animal):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def make_sound(self):
        print("Kukukuku!")

    def __str__(self):
        return f"I'm {self.name} the dove!"


print(Dove(name="Rexi", gender="Female"))

"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    הרעיון של מחלקה מופשטת כה נפוץ, שהמודול הפייתוני <var>abc</var> (קיצור של Abstract Base Class) מאפשר לנו להגדיר מחלקה כמופשטת.<br>
    נציץ ב<a href="https://docs.python.org/3/library/abc.html">תיעוד</a> של המודול וננסה לעקוב אחריו, למרות כל השטרודלים המוזרים שיש שם:
</p>

<ul style="text-align: right; direction: rtl; float: right; clear: both;">
    <li>המחלקה המופשטת שלנו תבצע ירושה מהמחלקה <var>ABC</var> שבמודול <var>abc</var>.</li>
    <li>מעל כל פעולה מופשטת (כזו שמשמשת רק לירושה ולא עומדת בפני עצמה) נוסיף <code dir="ltr">@abstractmethod</code>.</li>
</ul>
End of explanation
"""


Animal(name="Rexi", gender="Female")

"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    חדי העין ישימו לב שהחזרנו את פעולת האתחול <code dir="ltr">__init__</code> לכל תתי־המחלקות.<br>
    זה קרה כיוון שהגדרנו את <code dir="ltr">__init__</code> כפעולה מופשטת במחלקת <var>Animal</var>.<br>
    להגדרה של פעולה כמופשטת שתי השלכות מיידיות:
</p>

<ul style="text-align: right; direction: rtl; float: right; clear: both;">
    <li>תתי־המחלקות שיורשות מהמחלקה המופשטת, חייבות לממש את כל הפעולות שמוגדרות במחלקת־העל כמופשטות.</li>
    <li>גישה ישירה לפעולה המופשטת במחלקה שבה היא הוגדרה במקור כמופשטת, תביא לזריקת שגיאה.</li>
</ul>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    עכשיו כשנרצה ליצור מופע מ־<var>Animal</var>, נגלה שזה בלתי אפשרי, כיוון ש־<code dir="ltr">Animal.__init__</code> מוגדרת כמופשטת:
</p>
End of explanation
"""


class Fox(Animal):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    # What does the fox say?
    # (there is no make_sound method)

    def __str__(self):
        return f"I'm {self.name} the fox!"


Fox(name="Ylvis", gender="Male")

"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    אם ננסה ליצור תת־מחלקה שיורשת מ־<var>Animal</var> ולא מממשת את אחת הפעולות המופשטות שלה, נגלה שלא נוכל ליצור ממנה מופעים.<br>
    פייתון תזרוק שגיאה שאחת הפעולות המופשטות לא מוגדרת בתת־המחלקה:
</p>
End of explanation
"""


class Weapon:
    def __init__(self, strength, **kwargs):
        super().__init__(**kwargs)
        self.strength = strength

    def attack(self, enemy):
        enemy.decrease_health_points(self.strength)


class Vehicle:
    def __init__(self, max_speed, **kwargs):
        super().__init__(**kwargs)
        self.max_speed = max_speed

    def estimate_arrival_time(self, distance):
        return distance / (self.max_speed * 80 / 100)


class Tank(Weapon, Vehicle):
    def __init__(self, name, **kwargs):
        super().__init__(**kwargs)
        self.name = name

"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    עוד דבר מעניין שכדאי לשים לב אליו הוא השימוש הכבד ב־<code dir="ltr">**kwargs</code>.<br>
    כיוון שהפכנו את <code dir="ltr">__init__</code> למופשטת, אנחנו חייבים לממש אותה בכל תתי־המחלקות שיורשות מ־<var>Animal</var>.<br>
    למרות זאת, ה־<code dir="ltr">__init__</code> "המעניינת" שעושה השמות לתוך תכונות המופע היא זו של מחלקת־העל <var>Animal</var>,<br>
    זו שמקבלת את הפרמטרים <var>name</var> ו־<var>gender</var> ומשנה לפיהם את מצב המופע. 
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    ביצירת מופע של אחת מהחיות, נרצה להעביר את הפרמטרים <var>name</var> ו־<var>gender</var> שמיועדים ל־<code dir="ltr">Animal.__init__</code>.<br>
    אבל ביצירת מופע של יונה, של פרה או של כלב אנחנו נקרא בפועל ל־<code dir="ltr">__init__</code> שמימשו תתי־המחלקות.<br>
    בשלב הזה, תתי־המחלקות צריכות למצוא דרך לקבל את הפרמטרים הרלוונטיים ולהעביר אותם למחלקת־העל שעושה השמות ל־<var>self</var>.<br>
    כדי להעביר את כל הפרמטרים שמחלקת־העל צריכה, גם אם חתימת הפעולה <code dir="ltr">Animal.__init__</code> תשתנה בעתיד, אנחנו משתמשים ב־<code dir="ltr">**kwargs</code>.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    השימוש במחלקות מופשטות נפוץ בתכנות כדי לאפשר הרחבה נוחה של התוכנה.<br>
    מערכות שמאפשרות למתכנתים להרחיב את יכולותיהן בעזרת תוספים, לדוגמה, ישתמשו במחלקות מופשטות כדי להגדיר למתכנת דרך להתממשק עם הקוד של התוכנה.
</p>

<span style="text-align: right; direction: rtl; float: right; clear: both;">תרגיל ביניים: Friendliness Pellets</span>
<p style="text-align: right; direction: rtl; float: right; clear: both;">
    בחנות הפרחים של מושניק מוכרים זרי פרחים.<br>
    נמכר עם אגרטל או בלעדיו, ויש בו 3–30 פרחים מסוגים שונים: ורדים, סייפנים וסחלבים.<br>
    לכל אחד מהפרחים צבע שונה.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    המחיר של סייפן הוא 4 ש"ח ושל סחלב 10 ש"ח.<br>
    המחיר של ורד נקבע לפי הצבע שלו: ורד לבן עולה 5 ש"ח, וורד אדום עולה 6 ש"ח.<br>
    עבור זר עם אגרטל, על הלקוח להוסיף 20 ש"ח.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    כדי למשוך לקוחות חדשים, מדי פעם מושניק נותן הנחה על הזרים.<br>
    לכל אחת מההנחות מושניק מצרף הערה שמסבירה מה הסיבה שבגינה ניתנה ההנחה.<br>
    ישנם שני סוגים של הנחות בחנות: הנחה באחוזים והנחה שקלית.<br>
    לכל זר יכולות להיות כמה הנחות, אשר יחושבו לפי סדר צירופן.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    לדוגמה, אם הזר עלה 200 ש"ח ומושניק החליט לתת הנחה של 10 אחוזים, הזר יעלה כעת 180 ש"ח.<br>
    אם מושניק החליט לתת הנחה נוספת על הזר, הפעם של 30 ש"ח, מחירו של הזר יהיה כעת 150 ש"ח.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    אפשרו ללקוחות שרוצים חשבונית עבור רכישתם לקבל פירוט מודפס של חישוב המחיר של הזר.
</p>

<span style="text-align: right; direction: rtl; float: right; clear: both;">ירושה מרובה</span>
<p style="text-align: right; direction: rtl; float: right; clear: both;">
    ישר ולעניין – יש לי חדשות משוגעות עבורכם: כל מחלקה יכולה לרשת מיותר ממחלקה אחת!
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    ניצור מחלקה המייצגת כלי נשק, ומחלקה אחרת המייצגת כלי רכב.<br>
    מחלקת כלי הנשק תכלול את כוח כלי הנשק (<var>strength</var>) ופעולת תקיפה (<var>attack</var>).<br>
    מחלקת כלי הרכב תכלול את המהירות המרבית של כלי הרכב (<var>max_speed</var>) ופעולה של הערכת זמן הגעה משוער (<var>estimate_arrival_time</var>).
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    אם נרצה ליצור מחלקת טנק, לדוגמה, ייתכן שנרצה לרשת גם ממחלקת כלי הנשק וגם ממחלקת כלי הרכב.<br>
    פייתון מאפשרת לנו לעשות זאת בקלות יחסית:
</p>
End of explanation
"""


tank = Tank(name="Merkava Mk 4M Windbreaker", max_speed=64, strength=17.7)
distance = 101  # km to Eilat

# לטנק יש תכונות של טנק, של כלי נשק ושל כלי רכב
print(f"Tank name: {tank.name}")
print(f"Hours from here to Eilat: {tank.estimate_arrival_time(distance)}")
print(f"Weapon strength: {tank.strength}")

"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    בדוגמה שלמעלה מחלקת <var>Tank</var> יורשת את התכונות ואת הפעולות, הן של מחלקת <var>Vehicle</var> והן של מחלקת <var>Weapon</var>.<br>
    כל מה שהיינו צריכים לעשות זה לציין בסוגריים שאחרי המחלקה <var>Tank</var> את כל המחלקות שמהן אנחנו רוצים לרשת, כשהן מופרדות בפסיק זו מזו.<br>
    ניצור טנק לדוגמה, ונראה איך הוא קיבל את התכונות של כל מחלקות־העל שמהן הוא ירש:
</p>
End of explanation
"""


Tank.mro()

"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    לפני שנסביר בהרחבה איך הכול עובד, נעצור רגע כדי להגיד שירושה מרובה היא נושא שיחה נפיץ.<br>
    בגלל הסיבוכיות והשכבות הנוספות שהוא מוסיף לכל מופע, תמצאו מתכנתים רבים <a href="https://softwareengineering.stackexchange.com/q/218458/224112">שמתנגדים בתוקף לרעיון הירושה המרובה</a>.<br>
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    נציג בזריזות את אחת הבעיות הקלאסיות שנובעות מירושה מרובה – <a href="https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem">בעיית היהלום</a>.<br>
    בבעיה זו מוצגת תת־מחלקה שיורשת מ־2 מחלקות־על, שהן עצמן תת־מחלקות של מחלקה נוספת.<br>
    לדוגמה: מחלקת "כפתור" יורשת ממחלקת "לחיץ" וממחלקת "מלבן", שיורשות ממחלקת "אובייקט". 
</p>

<figure>
    <img src="images/diamond_problem.svg?v=0" style="max-width: 450px; margin-right: auto; margin-left: auto; text-align: center;" alt="עץ ירושה בצורת מעוין. בחלק העליון מוצגת המחלקה 'אובייקט', מתוכה יוצאים שני חצים למחלקות 'לחיץ' ו'מלבן' שנמצאות תחתיה. משתיהן יוצאים חצים לתת־המחלקה 'כפתור' שיורשת מהן וממוקמת הכי נמוך בתמונה."/>
    <figcaption style="margin-top: 2rem; text-align: center; direction: rtl;">
        עץ ירושה בצורת יהלום המדגים את הבעיה המתוארת.
    </figcaption>
</figure>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    אם גם "לחיץ" וגם "מלבן" מימשו את פעולת <code>__str__</code> אבל מחלקת "כפתור" לא, באיזו גרסה של <code>__str__</code> מחלקת "כפתור" צריכה להשתמש?<br>
    ומה בנוגע למצב שבו גם "לחיץ" וגם "אובייקט" מימשו את הפעולה?<br> 
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    כפי שוודאי הצלחתם להבין, ירושה מרובה יכולה להכניס אותנו להרבה "פינות" שלא מוגדרות היטב.<br>
    מהסיבה הזו פייתון החליטה לעשות מעשה, וליישר בכוח את העץ.<br>
    נביט בשרשרת הירושה של מחלקת <var>Tank</var>:
</p>
End of explanation
"""


class Clickable:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.clicks = 0

    def click(self):
        self.clicks = self.clicks + 1


class LargeRectangle:
    def __init__(self, width=10, height=5, **kwargs):
        super().__init__(**kwargs)
        self.width = width
        self.height = height
    
    def size(self):
        return self.width * self.height


class Button(Clickable, LargeRectangle):
    pass

"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    אף על פי שטכנית היינו מצפים לראות עץ שבו <var>Weapon</var> ו־<var>Vehicle</var> נמצאות באותה רמה ומצביעות על <var>Tank</var>,<br>
    בפועל פייתון "משטחת" את עץ הירושה כך ש־<var>Tank</var> יורשת מ־<var>Weapon</var> שיורשת מ־<var>Vehicle</var>.<br>
    הסדר של המחלקות לאחר השיטוח נקבע לפי אלגוריתם שנקרא <a href="https://en.wikipedia.org/wiki/C3_linearization">C3 Linearization</a>, אבל זה לא משהו שבאמת חשוב לדעת בשלב הזה.
</p>

<figure>
    <img src="images/multiple_inheritance.svg?v=4" style="max-width: 900px; margin-right: auto; margin-left: auto; text-align: center;" alt="בתמונה יש שני צדדים המופרדים בקו מקווקו. כותרת הצד הימני היא 'ירושה מרובה לפי הספר', ובה רואים את מחלקות Weapon ו־Vehicle באותו גובה, כששתיהן מצביעות ישירות למחלקת Tank. כותרת הצד השמאלי של התמונה היא 'ירושה בפייתון', ובה רואים את מחלקת Tank יורשת ממחלקת Vehicle שיורשת ממחלקת Weapon."/>
    <figcaption style="margin-top: 2rem; text-align: center; direction: rtl;">דוגמה להשטחה שפייתון עושה לירושה מרובה.
    </figcaption>
</figure>

<span style="text-align: right; direction: rtl; float: right; clear: both;">Mixin־ים</span>
<p style="text-align: right; direction: rtl; float: right; clear: both;">
    רעיון נפוץ המבוסס על ירושה מרובה הוא Mixin.<br>
    <dfn>Mixin</dfn> היא מחלקה שאין לה תכלית בפני עצמה, והיא קיימת כדי "לתרום" תכונות ופעולות למחלקה שתירש אותה.<br>
    לרוב נשתמש בשתי Mixins לפחות, כדי ליצור מהן מחלקות מורכבות יותר, שכוללות את הפעולות והתכונות של כל ה־Mixins.<br>
    לדוגמה: ניצור מחלקת "כפתור" (<var>Button</var>) שיורשת ממחלקת "מלבן גדול" (<var>LargeRectangle</var>) וממחלקת "לחיץ" (<var>Clickable</var>).
</p>
End of explanation
"""


buy_now = Button()
buy_now.click()
print(f"Button size (from LargeRectangle class): {buy_now.size()}")
print(f"Button clicks (from Clickable class): {buy_now.clicks}")

"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    ניצור כפתור ונראה שהוא אכן קיבל את התכונות והפעולות משתי המחלקות:
</p>
End of explanation
"""


from abc import ABC, abstractmethod


class Color:
    """Describe the game pieces' color"""
    BLACK = 0
    WHITE = 1

    def enemy_of(color):
        """Return the opponent color."""
        if color == Color.BLACK:
            return Color.WHITE
        return Color.BLACK


class Board:
    """Create and maintain the game board."""

    # Some functions below will not work well with altered board size.
    BOARD_SIZE = (8, 8)

    def __init__(self):
        self.reset()

    def get_square(self, row, col):
        """Return the game piece by its position on board.

        If there is no piece in this position, or if the position does
        not exist - return False.
        """
        if self.is_valid_square(row, col):
            return self.board[row][col]

    def set_square(self, row, col, piece):
        """Place piece on board."""
        self.board[row][col] = piece

    def is_valid_square(self, row, column):
        """Return True if square in board bounds, False otherwise."""
        row_exists = row in range(self.BOARD_SIZE[0])
        column_exists = column in range(self.BOARD_SIZE[1])
        return row_exists and column_exists

    def is_empty_square(self, square):
        """Return True if square is unoccupied, False otherwise.

        An empty square is a square which has no game piece on it.
        If the square is out of board bounds, we consider it empty.
        """
        return self.get_square(*square) is None

    def _generate_back_row(self, color):
        """Place player's first row pieces on board."""
        row_by_color = {Color.BLACK: 0, Color.WHITE: self.BOARD_SIZE[0] - 1}
        row = row_by_color[color]

        order = (Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook)
        params = {'color': color, 'row': row}
        return [order[i](col=i, **params) for i in range(self.BOARD_SIZE[0])]

    def _generate_pawns_row(self, color):
        """Place player's pawns row on board."""
        row_by_color = {Color.BLACK: 1, Color.WHITE: self.BOARD_SIZE[0] - 2}
        row = row_by_color[color]
        params = {'color': color, 'row': row}
        return [Pawn(col=i, **params) for i in range(self.BOARD_SIZE[0])]

    def get_pieces(self, color=None):
        """Yield the player's pieces.

        If color is unspecified (None), yield all pieces on board.
        """
        for row in self.board:
            for square in row:
                if square is not None and (color in (None, square.color)):
                    yield square

    def reset(self):
        """Set traditional board and pieces in initial positions."""
        self.board = [
            self._generate_back_row(Color.BLACK),
            self._generate_pawns_row(Color.BLACK),
            [None] * self.BOARD_SIZE[0],
            [None] * self.BOARD_SIZE[0],
            [None] * self.BOARD_SIZE[0],
            [None] * self.BOARD_SIZE[0],
            self._generate_pawns_row(Color.WHITE),
            self._generate_back_row(Color.WHITE),
        ]

    def move(self, source, destination):
        """Move a piece from its place to a designated location."""
        piece = self.get_square(*source)
        return piece.move(board=self, destination=destination)

    def __str__(self):
        """Return current state of the board for display purposes."""
        printable = ""
        for row in self.board:
            for col in row:
                if col is None:
                    printable = printable + " ▭ "
                else:
                    printable = printable + f" {col} "
            printable = printable + '\n'
        return printable


class Piece(ABC):
    """Represent a general chess piece."""

    def __init__(self, color, row, col, **kwargs):
        super().__init__(**kwargs)
        self.color = color
        self.row = row
        self.col = col
        self.moved = False
        self.directions = set()

    def is_possible_target(self, board, target):
        """Return True if the move is legal, False otherwise.

        A move is considered legal if the piece can move from its
        current location to the target location.
        """
        is_target_valid = board.is_valid_square(*target)
        is_empty_square = board.is_empty_square(target)
        is_hitting_enemy = self.is_enemy(board.get_square(*target))
        return is_target_valid and (is_empty_square or is_hitting_enemy)

    @abstractmethod
    def get_valid_moves(self, board):
        """Yield the valid target positions the piece can travel to."""
        pass

    def get_position(self):
        """Return piece current position."""
        return self.row, self.col

    def is_enemy(self, piece):
        """Return if the piece belongs to the opponent."""
        if piece is None:
            return False
        return piece.color != self.color

    def move(self, board, destination):
        """Change piece position on the board.

        Return True if the piece's position has successfully changed.
        Return False otherwise.
        """
        if not self.is_possible_target(board, destination):
            return False
        if destination not in self.get_valid_moves(board):
            return False

        board.set_square(*self.get_position(), None)
        board.set_square(*destination, self)
        self.row, self.col = destination
        self.moved = True
        return True

    def get_squares_threatens(self, board):
        """Get all the squares which this piece threatens.

        This is usually just where the piece can go, but sometimes
        the piece threat squares which are different than the squares
        it can travel to.
        """
        for move in self.get_valid_moves(board):
            yield move

    @abstractmethod
    def __str__(self):
        pass


class WalksDiagonallyMixin:
    """Define diagonal movement on the board.

    This mixin should be used only in a Piece subclasses.
    Its purpose is to add possible movement directions to a specific
    kind of game piece.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.directions.update({
            (-1, -1),          (1, -1),

            (-1,  1),          (1,  1),
        })


class WalksStraightMixin:
    """Define straight movement on the board.

    This mixin should be used only in a Piece subclasses.
    Its purpose is to add possible movement directions to a specific
    kind of game piece.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.directions.update({
                      (0, -1),
            (-1,  0),          (1,  0),
                      (0,  1),
        })


class WalksMultipleStepsMixin:
    """Define a same-direction, multiple-step movement on the board.

    This mixin should be used only on a Piece subclasses.
    Its purpose is to allow a piece to travel long distances based on a
    single-step pattern.

    For example, the bishop can move diagonally up to 7 squares per
    turn (in an orthodox chess game). This mixin allows it if the
    `directions` property is set to the 4 possible diagonal steps. It
    does so by overriding the get_valid_moves method and uses the
    instance `directions` property to determine the possible step for
    the piece.
    """

    def get_valid_moves(self, board, **kwargs):
        """Yield the valid target positions the piece can travel to."""
        for row_change, col_change in self.directions:
            steps = 1
            stop_searching_in_this_direction = False
            while not stop_searching_in_this_direction:
                new_row = self.row + row_change * steps
                new_col = self.col + col_change * steps
                target = (new_row, new_col)
                is_valid_target = self.is_possible_target(board, target)
                if is_valid_target:
                    yield target
                    steps = steps + 1
                    is_hit_enemy = self.is_enemy(board.get_square(*target))
                if not is_valid_target or (is_valid_target and is_hit_enemy):
                    stop_searching_in_this_direction = True


class Bishop(WalksDiagonallyMixin, WalksMultipleStepsMixin, Piece):
    """A classic Bishop chess piece.

    The bishop moves any number of blank squares diagonally.
    """
    def __str__(self):
        if self.color == Color.WHITE:
            return '♗'
        return '♝'


class Rook(WalksStraightMixin, WalksMultipleStepsMixin, Piece):
    """A classic Rook chess piece.

    The rook moves any number of blank squares straight.
    """
    def __str__(self):
        if self.color == Color.WHITE:
            return '♖'
        return '♜'


class Queen(
    WalksStraightMixin, WalksDiagonallyMixin, WalksMultipleStepsMixin, Piece,
):
    """A classic Queen chess piece.

    The queen moves any number of blank squares straight or diagonally.
    """
    def __str__(self):
        if self.color == Color.WHITE:
            return '♕'
        return '♛'


class Pawn(Piece):
    """A classic Pawn chess piece.

    A pawn moves straight forward one square, if that square is empty.
    If it has not yet moved, a pawn also has the option of moving two
    squares straight forward, provided both squares are empty.
    Pawns can only move forward.

    A pawn can capture an enemy piece on either of the two squares
    diagonally in front of the pawn. It cannot move to those squares if
    they are empty, nor to capture an enemy in front of it.

    A pawn can also be involved in en-passant or in promotion, which is
    yet to be implemented on this version of the game.
    """
    DIRECTION_BY_COLOR = {Color.BLACK: 1, Color.WHITE: -1}

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.forward = self.DIRECTION_BY_COLOR[self.color]

    def _get_regular_walk(self):
        """Return position after a single step forward."""
        return self.row + self.forward, self.col

    def _get_double_walk(self):
        """Return position after a double step forward."""
        src_row, src_col = self.get_position()
        return (src_row + self.forward * 2, src_col)

    def _get_diagonal_walks(self):
        """Returns position after a diagonal move.

        This only happens when hitting an enemy.
        It could also happen on "en-passant", which is
        unimplemented feature for now.
        """
        src_row, src_col = self.get_position()
        return (
            (src_row + self.forward, src_col + 1),
            (src_row + self.forward, src_col - 1),
        )

    def is_possible_target(self, board, target):
        """Return True if the Pawn's move is legal, False otherwise.

        This one is a bit more complicated than the usual case.
        Pawns can only move forward. They also can move two ranks
        forward if they have yet to move. Not like the other pieces,
        pawns can't hit the enemy using their regular movement. They
        have to hit it diagonally, and can't take a step forward if the
        enemy is just in front of them.
        """
        is_valid_move = board.is_valid_square(*target)
        is_step_forward = (
            board.is_empty_square(target)
            and target == self._get_regular_walk()
        )
        is_valid_double_step_forward = (
            board.is_empty_square(target)
            and not self.moved
            and target == self._get_double_walk()
            and self.is_possible_target(board, self._get_regular_walk())
        )
        is_hitting_enemy = (
            self.is_enemy(board.get_square(*target))
            and target in self._get_diagonal_walks()
        )
        return is_valid_move and (
            is_step_forward or is_valid_double_step_forward or is_hitting_enemy
        )

    def get_squares_threatens(self, board, **kwargs):
        """Get all the squares which the pawn can attack."""
        for square in self._get_diagonal_walks():
            if board.is_valid_square(*square):
                yield square

    def get_valid_moves(self, board, **kwargs):
        """Yield the valid target positions the piece can travel to.

        The Pawn case is a special one - see is_possible_target's
        documentation for further details.
        """
        targets = (
            self._get_regular_walk(),
            self._get_double_walk(),
            *self._get_diagonal_walks(),
        )
        for target in targets:
            if self.is_possible_target(board, target):
                yield target

    def __str__(self):
        if self.color == Color.WHITE:
            return '♙'
        return '♟'


class Knight(Piece):
    """A classic Knight chess piece.

    Can travel to the nearest square not on the same rank, file, or
    diagonal. It is not blocked by other pieces: it jumps to the new
    location.
    """
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.directions.update({
            (-2, 1), (-1, 2), (1, 2), (2, 1),  # Upper part
            (-2, -1), (-1, -2), (1, -2), (2, -1),  # Lower part
        })

    def get_valid_moves(self, board, **kwargs):
        super().get_valid_moves(board, **kwargs)
        for add_row, add_col in self.directions:
            target = (add_row + self.row, add_col + self.col)
            if self.is_possible_target(board, target):
                yield target

    def __str__(self):
        if self.color == Color.WHITE:
            return '♘'
        return '♞'


class King(WalksStraightMixin, WalksDiagonallyMixin, Piece):
    """A classic King chess piece.

    Can travel one step, either diagonally or straight.
    It cannot travel to places where he will be threatened.
    """

    def _get_threatened_squares(self, board):
        """Yield positions in which the king will be captured."""
        enemy = Color.enemy_of(self.color)
        for piece in board.get_pieces(color=enemy):
            for move in piece.get_squares_threatens(board):
                yield move

    def is_possible_target(self, board, target):
        """Return True if the king's move is legal, False otherwise.

        The king should not move to a square that the enemy threatens.
        """
        is_regular_valid = super().is_possible_target(board, target)
        threatened_squares = self._get_threatened_squares(board)
        return is_regular_valid and target not in threatened_squares

    def get_valid_moves(self, board, **kwargs):
        super().get_valid_moves(board, **kwargs)
        for add_row, add_col in self.directions:
            target = (add_row + self.row, add_col + self.col)
            if self.is_possible_target(board, target):
                yield target

    def get_squares_threatens(self, board):
        """Get all the squares that this piece may move to.

        This method is especially useful to see if other kings fall
        into this piece's territory. To prevent recursion, this
        function returns all squares we threat even if we can't go
        there.

        For example, take a scenario where the White Bishop is in B2,
        and the Black King is in B3. The White King is in D3, but it is
        allowed to go into C3 to threaten the black king if the white
        bishop protects it.
        """
        for direction in self.directions:
            row, col = self.get_position()
            row = row + direction[0]
            col = col + direction[1]
            if board.is_valid_square(row, col):
                yield (row, col)

    def __str__(self):
        if self.color == Color.WHITE:
            return '♔'
        return '♚'


"""
Explanation: <p style="text-align: right; direction: rtl; float: right; clear: both;">
    בחלק משפות התכנות האחרות ל־Mixins יש תחביר מיוחד, אולם בפייתון משתמשים פשוט בירושה מרובה.<br>
    מהסיבה הזו, בין היתר, ההבדל בין Mixins לבין ירושה מרובה עלול להיראות מעורפל מעט.<br>
    הדגש ב־Mixins הוא שהן מוגדרות כארגז תכונות או פעולות שאפשר לרשת, והן לא מיועדות לכך שיצרו ישירות מהן מופעים.<br>
    אפשר להגיד שכל מקרה של Mixins משתמש בירושה מרובה, אך לא כל מקרה של ירושה מרובה כולל Mixins.
</p>

<span style="text-align: right; direction: rtl; float: right; clear: both;">מונחים</span>
<dl style="text-align: right; direction: rtl; float: right; clear: both;">
    <dt>מחלקה מופשטת (Abstract Class)</dt>
    <dd>
        מחלקה שלא נהוג ליצור ממנה מופעים.<br>
        המחלקה המופשטת תגדיר פעולות ללא מימוש שנקראות "פעולות מופשטות".<br>
        המחלקות שיורשות מהמחלקה המופשטת יממשו את הפעולות הללו.<br>
    </dd>
    <dt>ירושה מרובה (Multiple Inheritance)</dt>
    <dd>
        ירושה שמחלקה מבצעת ממחלקות רבות, במטרה לקבל את תכונותיהן ופעולותיהן של כמה מחלקות־על. 
    </dd>
    <dt>Mixin</dt>
    <dd>
        מחלקה שמטרתה לספק תכונות ופעולות למחלקות היורשות אותה, ואין לה שימוש כשהיא עומדת בפני עצמה.<br>
        לרוב נעשה שימוש ביותר מ־Mixin אחת בעזרת ירושה מרובה. 
    </dd>
</dl>

<span style="text-align: right; direction: rtl; float: right; clear: both;">תרגיל לדוגמה</span>
<span style="text-align: right; direction: rtl; float: right; clear: both;">הקדמה</span>
<p style="text-align: right; direction: rtl; float: right; clear: both;">
    שחמט הוא משחק שבו שני שחקנים מתמודדים זה מול זה על לכידת מלכו של היריב.<br>
    לכל אחד מהשחקנים יש צבא שמורכב מכלי משחק, ועליהם לעשות בצבא שימוש מושכל כדי להשיג יתרון על פני השחקן השני.<br>
    כדי להבדיל בין כלי המשחק של השחקנים, כלי המשחק של שחקן אחד לבנים ואילו של השחקן השני שחורים.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    ממשו לוח של משחק שחמט בגודל 8 על 8 משבצות.<br>
</p>

<figure>
    <img src="images/chessboard.svg" style="max-width: 200px; margin-right: auto; margin-left: auto; text-align: center;" alt="תמונה של לוח שחמט בגודל 8 על 8, ללא כלים עליו. הריבוע השמאלי עליון הוא לבן, והריבועים הסמוכים אליו צבועים בחום. השורה העליונה ביותר ממוספרת 8, זו שתחתיה 7 וכן הלאה. השורה התחתונה ביותר ממוספרת 1. הטור השמאלי ביותר מסומן באות א, זה שמימינו מסומן באות ב, וכך הלאה. הטור הימני ביותר מסומן באות ח."/>
    <figcaption style="margin-top: 2rem; text-align: center; direction: rtl;">לוח שחמט סטנדרטי ללא כלים עליו.<br>מקור: ויקיפדיה.
    </figcaption>
</figure>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    בשחמט 6 סוגים של כלי משחק: רגלי, צריח, פרש, רץ, מלך ומלכה.<br>
    בפתיחת המשחק, שורות 1 ו־2 מלאות בכליו של השחקן הלבן ושורות 7 ו־8 מלאות בכליו של השחקן השחור.<br>
    הכלים מסודרים על הלוח כדלקמן:
</p>

<ul style="text-align: right; direction: rtl; float: right; clear: both;">
    <li>על כל המשבצות בשורה 2 ובשורה 7 מונחים רגלים.</li>
    <li>בשורות 1 ו־8, בטורים א ו־ח מונחים צריחים.</li>
    <li>בשורות 1 ו־8, בטורים ב ו־ז מונחים פרשים.</li>
    <li>בשורות 1 ו־8, בטורים ג ו־ו מונחים רצים.</li>
    <li>בשורות 1 ו־8, בטור ד מונחת המלכה.</li>
    <li>בשורות 1 ו־8, בטור ה מונח המלך.</li>
</ul>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    <a href="https://en.wikipedia.org/wiki/Rules_of_chess#Movement">חוקי התנועה</a> של הכלים מפורטים להלן:
</p>

<ul style="text-align: right; direction: rtl; float: right; clear: both;">
    <li>מלכה יכולה לנוע מספר בלתי מוגבל של משבצות לכל כיוון, לצדדים או באלכסון.</li>
    <li>צריח יכול לנוע מספר בלתי מוגבל של צעדים לצדדים: אנכי או אופקי.</li>
    <li>רץ יכול לנוע מספר בלתי מוגבל של צעדים באלכסון.</li>
    <li>פרש יכול לנוע שני צעדים אופקית ואז עוד צעד אנכית, או שני צעדים אנכית ואז עוד צעד אופקית.</li>
    <li>רגלי יכול לנוע צעד אחד לכיוון היריב. אם הוא עדיין לא נע ממקומו ההתחלתי, הוא יכול לבחור לעשות שני צעדים לכיוון היריב.</li>
    <li>מלך יכול לנוע לכל משבצת הסמוכה לו, כל עוד כלי היריב לא יכולים להגיע למשבצת הזו בתורם.</li>
</ul>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    כלי בתנועה לא יכול "לדלג" מעל כלי משחק אחרים – אם כלי נמצא בדרכו, הכלי שבתנועה לא יוכל לגשת למשבצות שנמצאות מעבר לאותו כלי חוסם.<br>
    יוצא דופן לכלל זה הוא פרש – שיכול לדלג מעל כלים אחרים.<br>
    אם הכלי החוסם הוא כלי של האויב, הכלי בתנועה רשאי לעבור ולעמוד על המשבצת של הכלי החוסם (נקרא גם "להכות אותו"), ולהסיר את הכלי החוסם מהמשחק.<br>
    יוצא דופן לכלל זה הוא רגלי – הוא לא יכול להכות חייל שחוסם אותו מלפנים, והוא כן יכול להכות חייל שנמצא באלכסון הימני או השמאלי של כיוון ההתקדמות שלו.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    השחמטאים שביניכם יכולים להתעלם כרגע ממהלכים "מיוחדים", כמו הכאה דרך הילוכו, הצרחה או הכתרה.<br>
    כמו כן, בינתיים נתעלם מהחוק שקובע שאם המלך מאוים, השחקן חייב לבצע מהלך שיסיר ממנו את האיום.
</p>

<span style="text-align: right; direction: rtl; float: right; clear: both;">התרגיל</span>
<p style="text-align: right; direction: rtl; float: right; clear: both;">
    ממשו מחלקת <var>Board</var> שתכיל תכונה בשם <var>board</var>.<br>
    ביצירת הלוח, ייווצר לוח תקני מאויש בכלי משחק כפי שהוסבר לעיל.
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    לצורך כך, צרו מחלקה כללית בשם <var>Piece</var> המייצגת כלי משחק בשחמט.<br>
    לכל כלי משחק צבע (<var>color</var>), השורה שבה הוא נמצא (<var>row</var>) והעמודה שבה הוא נמצא (<var>column</var>).<br>
    צרו גם מחלקות עבור כל אחד מהכלים: <var>Pawn</var> (רגלי), <var>Rook</var> (צריח), <var>Knight</var> (פרש), <var>Bishop</var> (רץ), <var>Queen</var> (מלכה) ו־<var>King</var> (מלך).<br>
</p>

<p style="text-align: right; direction: rtl; float: right; clear: both;">
    אפשרו לכל אחד מכלי המשחק לזוז על הלוח לפי החוקיות שתוארה מעלה.<br>
    דאגו שקריאה ל־<var>Board</var> תדפיס לוח ועליו כלי משחק לפי המצב העדכני של הלוח.
</p>

<span style="text-align: right; direction: rtl; float: right; clear: both;">פתרון אפשרי</span>
<p style="text-align: right; direction: rtl; float: right; clear: both;">
    נפתח ונאמר שאם אתם אמיצים במידה מספקת – אנחנו ממליצים לכם לנסות לפתור את התרגיל בעצמכם.<br>
    הוא בהחלט לא פשוט ועלול לקחת לא מעט זמן, ולכן הפתרון שלנו מוגש פה:
</p>
End of explanation
"""


