بلک‌جک

بلک‌جک

تمرین یادگیری

مقدمه

مقایسه

Python از عملگرهای مقایسه‌ی پایه‌ی زیر پشتیبانی می‌کند:

عملگر عمل توضیح
> «بزرگ‌تر از» a > b برابر با True است اگر مقدار a به‌طور اکید بزرگ‌تر از b باشد
< «کوچک‌تر از» a < b برابر با True است اگر مقدار a به‌طور اکید کوچک‌تر از b باشد
== «برابر با» a == b برابر با True است اگر مقدار a به‌طور اکید برابر با b باشد
>= «بزرگ‌تر یا مساوی» a >= b برابر با True است اگر مقدار a > b یا a == b باشد
<= «کوچک‌تر یا مساوی» a <= b برابر با True است اگر مقدار a < b یا a == b باشد
!= «برابر نبودن» a != b برابر با True است اگر a == b برابر با False باشد
is «هویت» a is b برابر با True است اگر و تنها اگر a و b همان شیء یکسان باشند
is not «نقیض هویت» a is not b برابر با True است اگر a و b همان شیء نباشند
in «آزمون عضویت» a in b برابر با True است اگر a عضو، زیرمجموعه یا عنصری از b باشد
not in «آزمون نقیض عضویت» a not in b برابر با True است اگر a عضو، زیرمجموعه یا عنصری از b نباشد

همه‌ی آن‌ها اولویت یکسانی دارند (که از اولویت عملیات منطقی بالاتر، ولی از اولویت عملیات حسابی یا بیتی پایین‌تر است).

مقایسه بین انواع داده‌ی مختلف

اشیائی که از انواع متفاوتی هستند (به‌جز انواع عددی) به‌طور پیش‌فرض هرگز برابر مقایسه نمی‌شوند. نمونه‌های غیریکسان یک class هم برابر مقایسه نمی‌شوند، مگر آنکه class متدهای ویژه‌ی مقایسه‌ی غنی را تعریف کند که رفتار پیش‌فرض مقایسه‌ی object را سفارشی‌سازی می‌کنند. سفارشی‌سازی از طریق rich comparisons در یک تمرین بعدی پوشش داده می‌شود. برای جزئیات (بسیار) بیشتر در این مورد، به مقایسه‌ی مقادیر در مستندات Python مراجعه کنید.

انواع عددی (بیشتر اوقات) استثنایی بر این قاعده‌ی تطبیق نوع هستند. یک integer می‌تواند برابر با یک float در نظر گرفته شود (یا یک octal برابر با یک hexadecimal)، به شرطی که این انواع بتوانند به‌صورت ضمنی برای مقایسه تبدیل شوند.

برای دیگر انواع عددی در کتابخانه‌ی استاندارد Python (complex، decimal، fractions)، عملگرهای مقایسه در جایی تعریف می‌شوند که «معنا دارند» (جایی که تبدیل ضمنی نتیجه را تغییر نمی‌دهد)، ولی اگر اشیاء زیربنایی نتوانند به‌درستی برای مقایسه تبدیل شوند، یک TypeError پرتاب می‌کنند. برای اطلاعات بیشتر درباره‌ی قواعدی که Python برای تبدیل عددی به کار می‌برد، به تبدیل‌های حسابی در مستندات Python مراجعه کنید.

>>> import fractions

# A string cannot be converted to an int.
>>> 17 == '17'
False

# An int can be converted to float for comparison.
>>> 17 == 17.0
True

# The fraction 6/3 can be converted to the int 2
# The int 2 can be converted to 0b10 in binary.
>>> 6/3 == 0b10
True

# An int can be converted to a complex number with a 0 imaginary part.
>>> 17 == complex(17)
True

# The fraction 2/5 can be converted to the float 0.4
>>> 0.4 == 2/5
True

>>> complex(2/5, 1/2) == complex(0.4, 0.5)
True

هر مقایسه‌ی ترتیبی یک عدد با یک نوع NaN (نه یک عدد) برابر با False است. یک عارضه‌ی گیج‌کننده‌ی تعریف NaN در Python این است که NaN هرگز برابر با NaN مقایسه نمی‌شود.

>>> x = float('NaN')

>>> 3 < x
False

>>> x < 3
False

# NaN never compares equal to NaN
>>> x == x
False

مقایسه‌ی رشته‌ها

برخلاف اعداد، رشته‌ها (str) به‌صورت واژه‌نامه‌ای مقایسه می‌شوند، با استفاده از نقاط کد یونیکد تک‌تک آن‌ها (یعنی نتیجه‌ی سپردن هر نقطه‌ی کد در str به تابع توکار ord()، که یک int برمی‌گرداند). اگر همه‌ی نقاط کد در هر دو رشته مطابقت داشته باشند و به یک ترتیب باشند، آن دو رشته برابر در نظر گرفته می‌شوند. این مقایسه به‌شکل «جفت‌به‌جفت» انجام می‌شود، اولی‌بااولی، دومی‌بادومی و به همین ترتیب. در Python 3.x، str و bytes را نمی‌توان مستقیماً هم‌تبدیل یا مقایسه کرد.

>>> 'Python' > 'Rust'
False

>>> 'Python' > 'JavaScript'
True

# Examples with Mandarin.
# hello < goodbye
>>> '你好' < '再见'
True

# ord() of first characters
>>> ord('你'), ord('再')
(20320, 20877)

# ord() of second characters
>>> ord('好'), ord('见')
(22909, 35265)

# And with Korean words.
# Pretty < beautiful.
>>> '예쁜' < '아름다운'
False

>>> ord('예'), ord('아')
(50696, 50500)

زنجیره‌سازی مقایسه

عملگرهای مقایسه را می‌توان به‌صورت دلبخواه زنجیره کرد، یعنی می‌توان آن‌ها را در هر ترکیبی با هر طولی به کار برد. توجه داشته باشید که ارزیابی یک عبارت از left به right انجام می‌شود.

به‌عنوان مثال، x < y <= z معادل x < y and y <= z است، با این تفاوت که y فقط یک‌بار ارزیابی می‌شود. در هر دو حالت، وقتی x < y برابر با False تشخیص داده شود، z اصلاً ارزیابی نمی‌شود. به این کار اغلب short-circuit evaluation می‌گویند: اگر ارزش درستیِ عبارت پیش‌تر مشخص شده باشد، ارزیابی متوقف می‌شود.

Short circuiting توسط عملگرهای منطقی و توابع مختلف، و همچنین توسط زنجیره‌سازی مقایسه در Python پشتیبانی می‌شود. برخلاف بسیاری از زبان‌های برنامه‌نویسی دیگر، از جمله C، C++، C# و Java، عبارت‌های زنجیره‌ای مانند a < b < c در Python یک تفسیر ریاضی و اولویت متعارف دارند.

>>> x = 2
>>> y = 5
>>> z = 10

>>> x < y < z
True

>>> x < y > z
False

>>> x > y < z
False

مقایسه‌ی هویت شیء

عملگرهای is و is not هویت شیء را می‌آزمایند، در مقابل مقدار شیء. هویت یک شیء پس از ساخته‌شدن هرگز تغییر نمی‌کند و می‌توان آن را با استفاده از تابع id() یافت.

<apple> is <orange> اگر و تنها اگر id(<apple>) == id(<orange>) باشد، برابر با True ارزیابی می‌شود. <apple> is not <orange> نتیجه‌ی معکوس را می‌دهد.

به دلیل وضعیت تک‌نمونه‌ای‌شان، None و NotImplemented باید همیشه با استفاده از is و is not با عناصر مقایسه شوند. برای جزئیات بیشتر درباره‌ی این قرارداد، مستندات مرجع Python درباره‌ی مقایسه‌ی مقادیر و PEP8 را ببینید.

>>> my_fav_numbers = [1, 2, 3]

>>> your_fav_numbers = my_fav_numbers

>>> my_fav_numbers is your_fav_numbers
True

# The returned id will differ by system and python version.
>>> id(my_fav_numbers)
4517478208

# your_fav_numbers is only an alias pointing to the original my_fav_numbers object.
# Assigning a new name does not create a new object.
>>> id(your_fav_numbers)
4517478208


>>> my_fav_numbers is not your_fav_numbers
False

>>> my_fav_numbers is not None
True

>>> my_fav_numbers is NotImplemented
False

مقایسه‌های عضویت

عملگرهای in و not in عضویت را می‌آزمایند. <fish> in <soup> اگر <fish> عضوی از <soup> باشد (اگر <fish> زیرمجموعه‌ای از <soup> باشد یا درون آن قرار گیرد)، برابر با True ارزیابی می‌شود و در غیر این صورت False ارزیابی می‌شود. <fish> not in <soup> نقیض، یا معکوسِ <fish> in <soup> را برمی‌گرداند.

برای انواع رشته و بایت، <name> in <fullname> اگر و تنها اگر <name> زیررشته‌ای از <fullname> باشد، برابر با True است.

# A set of lucky numbers.
>>> lucky_numbers = {11, 22, 33}
>>> 22 in lucky_numbers
True

>>> 44 in lucky_numbers
False

# A dictionary of employee information.
>>> employee = {'name': 'John Doe', 
                'id': 67826, 'age': 33, 
                'title': 'ceo'}

# Checking for the membership of certain keys.
>>> 'age' in employee
True

>>> 33 in employee
False

>>> 'lastname' not in employee
True

# Checking for substring membership
>>> name = 'Super Batman'
>>> 'Bat' in name
True

>>> 'Batwoman' in name
False

دستورالعمل‌ها

در این تمرین قصد دارید برخی از قواعد بلک‌جک را پیاده‌سازی کنید، مانند شیوه‌ی انجام و امتیازدهی بازی.

نکته: در این تمرین، حرف A یعنی آس، حرف J یعنی سرباز، حرف Q یعنی بی‌بی و حرف K یعنی شاه. جوکرها کنار گذاشته می‌شوند. یک دسته‌ی کارت ۵۲تایی استاندارد با خال‌های فرانسوی در نظر گرفته می‌شود، اما در بیشتر نسخه‌ها، برای بازی چند دسته کارت را با هم مخلوط می‌کنند.

1. محاسبه‌ی ارزش یک کارت

در بلک‌جک، اینکه آس ۱ امتیاز ارزش داشته باشد یا ۱۱، به خودِ بازیکن بستگی دارد (بعداً بیشتر درباره‌ی این صحبت می‌کنیم). کارت‌های تصویری (J، Q، K) ۱۰ امتیاز دارند و ارزش هر کارت دیگری برابر با عدد روی آن (مقدار عددی) است.

تابع value_of_card(<card>) را با پارامتر card تعریف کنید. تابع باید ارزش عددی رشته‌ی کارتِ ورودی را برگرداند. از آنجا که یک آس می‌تواند چند مقدار بگیرد (۱ یا ۱۱)، این تابع باید فعلاً ارزش کارت آس را ۱ در نظر بگیرد. بعداً تابعی پیاده‌سازی خواهید کرد که با در نظر گرفتن دستِ موجود، ارزش کارت آس را مشخص می‌کند.

>>> value_of_card('K')
10

>>> value_of_card('4')
4

>>> value_of_card('A')
1

2. تعیین اینکه کدام کارت ارزش بیشتری دارد

تابع higher_card(<card_one>, <card_two>) را با پارامترهای card_one و card_two تعریف کنید. برای امتیازدهی، ارزش J، Q یا K برابر ۱۰ است. تابع باید برگرداند کدام کارت از نظر امتیازدهی ارزش بیشتری دارد. اگر ارزش هر دو کارت برابر باشد، هر دو را برگردانید. برگرداندن هر دو کارت با استفاده از یک کاما در دستور return انجام می‌شود:

# Using a comma in a return creates a Tuple.  Tuples will be covered in a later exercise.
>>> def returning_two_values(value_one, value_two):
        return value_one, value_two

>>> returning_two_values('K', '3')
('K', '3')

آس می‌تواند چند مقدار بگیرد، بنابراین در این تمرین کارت‌های A را با ارزش ۱ در نظر می‌گیریم.

>>> higher_card('K', '10')
('K', '10')

>>> higher_card('4', '6')
'6'

>>> higher_card('K', 'A')
'K'

3. محاسبه‌ی ارزش یک آس

همان‌طور که پیش‌تر گفتیم، یک آس می‌تواند یا ۱ امتیاز ارزش داشته باشد یا ۱۱ امتیاز. بازیکنان تلاش می‌کنند تا حد امکان به امتیاز ۲۱ نزدیک شوند، بدون آنکه از ۲۱ فراتر بروند («سوختن»).

تابع value_of_ace(<card_one>, <card_two>) را با پارامترهای card_one و card_two تعریف کنید؛ این‌ها جفتی از کارت‌های موجود در دست، پیش از گرفتن کارت آس هستند. تابع شما باید تصمیم بگیرد که آسِ بعدی ارزش ۱ می‌گیرد یا ۱۱، و همان مقدار را برگرداند. یادتان باشد: ارزش دست همراه با آس باید تا حد امکان بالا باشد، بدون آنکه از ۲۱ فراتر برود.

راهنما: اگر از قبل آسی در دست داشته باشیم، ارزش آسِ بعدی ۱ خواهد بود.

>>> value_of_ace('6', 'K')
1

>>> value_of_ace('7', '3')
11

4. تشخیص دستِ «نچرال» یا «بلک‌جک»

اگر در دو کارت اول، یک آس (A) و یک کارت ده (10، K، Q یا J) به بازیکن داده شود، بازیکن امتیاز ۲۱ دارد. به این دست، دستِ بلک‌جک می‌گویند.

تابع is_blackjack(<card_one>, <card_two>) را با پارامترهای card_one و card_two تعریف کنید که جفتی از کارت‌ها هستند. مشخص کنید که آیا این دستِ دوکارتی یک بلک‌جک است و اگر هست، مقدار «منطقی» True و در غیر این صورت False را برگردانید.

نکته: محاسبه‌ی امتیاز را می‌توان به روش‌های زیادی انجام داد. اما اگر ممکن باشد، ترجیح می‌دهیم بررسی کنید که آیا یک آس و یک کارت ده در دست وجود دارند (یا در موقعیت مشخصی قرار دارند)، نه اینکه ارزش‌های دست را جمع کنید.

>>> is_blackjack('A', 'K')
True

>>> is_blackjack('10', '9')
False

5. تقسیم جفت‌ها

اگر دو کارت اول یک دست ارزش یکسانی داشته باشند (مثلاً دو شش، یا یک Q و K)، بازیکن می‌تواند آن‌ها را به‌عنوان دو دست جداگانه در نظر بگیرد. به این کار «تقسیم جفت‌ها» می‌گویند.

تابع can_split_pairs(<card_one>, <card_two>) را با پارامترهای card_one و card_two تعریف کنید که جفتی از کارت‌ها هستند. مشخص کنید که آیا این دستِ دوکارتی می‌تواند به دو جفت تقسیم شود. اگر این دست قابل تقسیم باشد، مقدار منطقی True و در غیر این صورت False را برگردانید.

>>> can_split_pairs('Q', 'K')
True

>>> can_split_pairs('10', 'A')
False

6. دو برابر کردن شرط

وقتی مجموع دو کارت اولیه‌ای که داده می‌شود ۹، ۱۰ یا ۱۱ امتیاز باشد، بازیکن می‌تواند شرطی اضافی به اندازه‌ی شرط اولیه‌ی خود بگذارد. به این کار «دو برابر کردن شرط» می‌گویند.

تابع can_double_down(<card_one>, <card_two>) را با پارامترهای card_one و card_two تعریف کنید که جفتی از کارت‌ها هستند. مشخص کنید که آیا این دستِ دوکارتی می‌تواند «دو برابر» شود و اگر می‌تواند، مقدار منطقی True و در غیر این صورت False را برگردانید.

>>> can_double_down('A', '9')
True

>>> can_double_down('10', '2')
False
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
Python Exercism

آماده‌اید بلک‌جک را شروع کنید؟

در Exercism ثبت‌نام کنید تا Python را همراه با 17 مفهوم146 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.