--------------------------------------------------
میخواهیم تابعی بنویسیم که کارش چک کردن شرایط تعریف شده برای اشیاء (*objects*) مورد نظر است.
--------------------------------------------------
*کریم کچل، شطرنجباز حرفهای محله است که بعد از شرایط کرونا خود را در خانه قرنطینه کرده. اما متاسفانه مهرههای شطرنجش را گم کرده و بابت شرایط کرونا نمیتواند مهرهها را از بیرون سفارش دهد و تصمیم گرفته که با میوههایی که در خانه دارد مهرهها را بسازد.*
*او میفهمد که هر میوهای را نمیتواند به عنوان مهرهی شطرنج استفاده کند و تنها میوههای خوب را میتواند به عنوان مهرهی شطرنج استفاده کند.*
میوهای خوب است که شرایط زیر را داشته باشد:
- شکل آن به صورت کروی (*sphere*) باشد.
- جرم آن بین ۳۰۰ تا ۶۰۰ گرم باشد.
- حجم آن بین ۱۰۰ تا ۵۰۰ سانتیمتر مکعب باشد.
توجه کنید که جرم میوههای خوب، خود اعداد ۳۰۰ و ۶۰۰ و حجم آن، خود اعداد ۱۰۰ و ۵۰۰ هم میتواند باشد.
حال شما باید تابعی بنویسید که با گرفتن لیستی از میوهها، مشخص کند از هر نوع میوه چند تای آن خوب است.
# جزئیات
+ تابع شما که باید به نام `fruits` باشد به عنوان ورودی لیستی(*tuple*) از لغتنامهها(*Dictionary*) دریافت میکند که هر لغتنامه اطلاعات مربوط به یک میوه میباشد. این تابع در نهایت باید تعداد هر کدام از میوههای خوب را در قالب یک لغتنامه برگرداند.
+ نام و شکل هر میوه به ترتیب با عنوان `name` و `shape` و جرم و حجم هر میوه به ترتیب با عنوان `mass` و `volume` در یک لغتنامه ذخیره شده است.
+ فرض کنید ۴ عدد میوه به صورت زیر داریم:
| نام میوه | شکل | جرم | حجم | وضیعت |
|:--------:|:---------:|:----------:|:----------:|:----------:|
| سیب | کروی | ۳۵۰ | ۱۲۰ | میوه خوب |
| انبه | مربعی | ۱۵۰ | ۱۲۰ | میوه بد |
| لیمو | کروی | ۳۰۰ | ۱۰۰ | میوه خوب |
| سیب | کروی | ۵۰۰ | ۲۵۰ | میوه خوب |
سیستم داوری برای ورودی دادن میوههای بالا به تابع `fruits` این تابع را به صورت زیر صدا میزند.
```python
output = fruits ((
{'name':'apple', 'shape': 'sphere', 'mass': 350, 'volume': 120},
{'name':'mango', 'shape': 'square', 'mass': 150, 'volume': 120},
{'name':'lemon', 'shape': 'sphere', 'mass': 300, 'volume': 100},
{'name':'apple', 'shape': 'sphere', 'mass': 500, 'volume': 250}))
```
در بین این میوهها، سه تا میوه خوب وجود دارد که ۲ تای آنها سیب، و یکی از آنها لیمو است. خروجی تابع شما به ازای چنین چیزی باید یک لغتنامه به صورت زیر باشد:
```python
{'apple': 2, 'lemon': 1}
```
ساختار تابع شما باید به شکل زیر باشد:
```python
def fruits(tuple_of_fruits):
pass
```
# نکات
+ برای ارسال باید یک فایل پایتون ارسال کنید که در آن تابع `fruits` به شکل گفته شده وجود داشته باشد.
+ میتوانید فایل اولیهی خام و کد تست نمونه را با استفاده از این [لینک](https://quera.ir/qbox/download/wlvI9vXrkU/initial.zip) دانلود کنید.
# قسمت آموزشی
در این قسمت راهنماییهای سوال به ترتیب در روزهای شنبه، دوشنبه و چهارشنبه ساعت ۱۸ اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش ["سوال بپرسید"](https://quera.ir/contest/clarification/18303/) مطرح کنید.
<details class="blue">
<summary>راهنمایی ۱</summary>
در قدم اول، سوال را ساده میکنیم و فرض میکنیم که میخواهیم تابعی بنویسیم که یک میوه را در قالب یک کتابخانه ورودی میگیرد و بررسی میکند خوب است یا نه.
برای این کار تلاش میکنیم چنین تابعی را با توجه به شرایط سوال کامل کنیم:
```python
def is_good(fruit):
#returns true if the fruit is good
```
اگر چنین تابعی را داشته باشیم میتوانیم میوهها را تکتک به آن بدهیم و بررسی کنیم خوب هستند یا نه.
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
تابع `is_good` را به شکل زیر پیادهسازی میکنیم
```python
def is_good(fruit):
if fruit['shape'] == 'sphere' and 300 <= fruit['mass'] <= 600 and 100 <= fruit['volume'] <= 500:
return True # Fruit is good
else:
return False # Fruit is bad
```
حالا باید در تابع خواسته شده در سوال تک به تک میوه ها رو با این روش بررسی کنیم تا ببینیم خوب هستند یا نه و لغتنامهمان را آپدیت کنیم:
```python
def fruits(fruits_list):
dict = {}
for fruit in fruits_list:
if is_good(fruit):
# Update dictionary
return dict
```
</details>
<details class="blue">
<summary>راهنمایی ۳</summary>
حالا که هم میتونیم میوههای خوب و بد رو از هم تشخیص بدیم و تک تک میوههامون رو هم بهش میدیم وقت این شده که لغتنامهمان را آپدیت کنیم!
برای اینکار اول چک میکنیم ببینیم میوه خوبمون وجود داره تو لغتنامه یا نه اگر از قبل وجود داشتش به مقدارش یدونه اضافه میکنیم اگر هم وجود نداشت با مقدار یک به لغتنامهمان اضافهش میکنیم!
در نهایت کدمون مثل کد پایین میشه:
```python
def is_good(fruit):
if fruit['shape'] == 'sphere' and 300 <= fruit['mass'] <= 600 and 100 <= fruit['volume'] <= 500:
return True # Fruit is good
else:
return False # Fruit is bad
def fruits(fruits_list):
dict = {}
for fruit in fruits_list:
if is_good(fruit):
if fruit['name'] in dict:
dict[fruit['name']] += 1
else:
dict[fruit['name']] = 1
return dict
```
موفق باشید :دی
</details>
شطرنج میوهای
--------------------------------------------------
میخواهیم تابعی بنویسیم که تعداد لینکهای یک فایل *HTML* را محاسبه کند.
--------------------------------------------------
*کریم که نتوانست شطرنج فیزیکی خود را درست کند به اینترنت روی آورده و در این روزهای قرنطینه تصمیم گرفته که با استفاده از اینترنت و فضای آنلاین شطرنج بازی کند. در همان ابتدای استفاده از محیط اینترنت کریم عاشق شمردن تعداد لینکهای صفحهها شده و میخواهد برای صفحه داده شده تعداد لینکها را بشمرد.*
هدف ما این است که برنامهای بنویسیم که کار او را راحت کند و با گرفتن یک فایل *HTML* تعداد لینکهای آن صفحه را محاسبه کند.
# جزئیات
+ برنامهی شما باید شامل یک تابع به نام `process` باشد.
+ تابع `process` به عنوان ورودی نام فایل *HTML* مورد نظر را دریافت میکند و تعداد لینکهای این فایل را برمیگرداند (توجه کنید که فایل به صورت لوکال در کنار تستها وجود دارد و شما باید آن را `open` کرده و اطلاعاتش را بخوانید).
+ لینک به معنای زنجیر است و برای هدایت کردن کاربران از صفحه جاری به صفحهای دیگر استفاده میشود. به این صورت که متن مورد نظر خود را می نویسیم و کاری میکنیم که وقتی کاربران روی آن کلیک کردند به صفحه مورد نظر ما هدایت شوند.
+ برای ایجاد لینک در *HTML* از تگ `a` استفاده میکنیم به طور مثال:
``` html
<a href="https://quera.ir">کلیک کنید</a>
```
در لینک بالا، عبارتی که برای `href` در نظر گرفته می شود، همان آدرس صفحهی مقصد است که می خواهیم کاربر پس از کلیک بر روی متن "کلیک کنید" به آن هدایت شود.
+ در واقع شما باید تعداد تگهای `a` فایل *HTML* داده شده را محاسبه کنید.
+ در این سوال میتوانید از کتابخانههای مختلف پایتون استفاده کنید و نام آن کتابخانهها را طبق روشی که در قسمت نکات آمده بنویسید تا در هنگام کدنویسی بتوانید آنها را `import` کرده و از آن استفاده کنید.
# نکات
+ نام فایل پایتون شما باید `solution.py` باشد.
+ تستهای اصلی این سوال، صفحات اصلی سایتهای مختلف هستند و برنامه شما باید توانایی کارکردن روی همه صفحات را داشته باشد.
+ میتوانید فایل اولیهی خام و کد تست نمونه را با استفاده از این [لینک](https://quera.ir/qbox/download/jxOBgpIEb0/chesstml.zip) دانلود کنید.
+ برای استفاده از کتابخانههای مختلف میتوانید همراه فایل ارسالی، فایلی به نام `python_requirements.txt` بگذارید که در آن نام کتابخانههای مورد نیاز و شمارهٔ نسخهٔ آنها به فرمت زیر در آن موجود باشد: (اگر شماره نسخه را ننویسید آخرین نسخه آن کتابخانه نصب میشود)
```
firstlib==1.2.3
secondlib==4.5.6
...
```
# قسمت آموزشی
در این قسمت راهنماییهای سوال به ترتیب در روزهای شنبه، دوشنبه و چهارشنبه ساعت ۱۸ اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش ["سوال بپرسید"](https://quera.ir/contest/clarification/18303/) مطرح کنید.
<details class="blue">
<summary>راهنمایی ۱</summary>
سعی کنید از کتابخانههای خارجی برای حل این سوال استفاده کنید. میتوانید با سرچ در گوگل کتابخانههای خوبی برای این کار پیدا کنید.
ما استفاده از کتابخانه `beautifulsoup` را پیشنهاد میکنیم که یکی از کتابخانههای خوب برای حل این سوال است!
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
اول از همه کتابخانه `BeautifulSoup` را ایمپورت میکنیم.
حالا باید آدرس داده شده در تابع رو باز کنیم و با استفاده از `beautifulsoup` اون رو بخونیم و سعی کنیم از این کتابخونه برای پیدا کردن تعداد لینکها استفاده کنیم و در آخر تعداد لینکها رو برگردونیم!
```python
from bs4 import BeautifulSoup
def process(path):
with open(path) as html:
soup = BeautifulSoup(html.read(), 'html')
links = []
# Find all links
return len(links)
```
</details>
<details class="blue">
<summary>راهنمایی ۳</summary>
در این مرحله باید تمام `href` ها رو از تگهای `a` از طریق کتابخانه `BeautifulSoup` پیدا کنیم که به روش زیر برای آن اقدام میکنیم:
```python
for link in soup.find_all('a'):
links.append(link.get('href'))
```
و در نهایت کد ما به صورت زیر میشه:
```python
from bs4 import BeautifulSoup
def process(path):
with open(path) as html:
soup = BeautifulSoup(html.read(), 'html')
links = []
for link in soup.find_all('a'):
links.append(link.get('href'))
return len(links)
```
موفق باشید :دی
</details>
chesstml
--------------------------------------------------
میخواهیم تابعی بنویسیم که بر روی [تولیدکنندهها](https://wiki.python.org/moin/Generators) (`generator`) پیمایش کند و به آنها اطلاعاتی ارسال کند.
--------------------------------------------------
*متاسفانه کریم اینترنتش قطع شده و با میوههایش هم نتوانست مهرههای شطرنج را بسازد و تصمیم گرفت بخوابد تا شاید زمان بگذرد. کریم خواب دید که مسئول یک مسابقهی برنامهنویسی شده است.*
او از شرکتکنندگان خواسته یک تولیدکننده یا `generator` بنویسند که عدد مطلوبی را حدس بزند؛ این تولیدکننده وضعیت (بزرگتر، کوچکتر یا مساوی بودن) عدد حدسی، نسبت به عدد مطلوب را از پیمایشکننده خود دریافت میکند.
کریم به عنوان مسئول مسابقه باید این تولیدکنندهها را بررسی کند، اما چون توانایی برنامهنویسی در کریم مشاهده نمیشود، از شما کمک میخواهد که تابعی به عنوان پیمایشکننده برای بررسی این تولیدکنندهها بنویسید.
# پروژه اولیه
پروژه اولیه را از [اینجا](http://bayanbox.ir/download/128931233525929665/guess-generator.zip) دانلود کنید.
ساختار فایلهای این پروژه به صورت زیر است.
```
guess-generator
├── generators.py
└── source.py
```
# جزئیات
تولیدکنندهها انتظار دارند مقادیر زیر را از پیمایشکننده خود دریافت کنند:
- `G`: عدد حدسی از عدد مطلوب بزرگتر باشد.
- `L`: عدد حدسی از عدد مطلوب کوچکتر باشد.
- `E`: عدد حدسی با عدد مطلوب برابر باشد.
یک نمونه از تولیدکننده مورد نظر:
```python
import random
def guess_generator(min_value, max_value):
num = random.randint(min_value, max_value + 1)
resp = (yield num)
while resp != 'E': # Equal
if resp == 'G': # Greater
max_value = num - 1
elif resp == 'L': # Less
min_value = num + 1
num = random.randint(min_value, max_value + 1)
resp = (yield num)
```
شما باید تابعی بنویسید که پارامترهای زیر را دریافت کند:
- `guess_generator`: تولیدکنندهای که باید پیمایش شود.
- `min_value`: حداقل مقدار برای حدس
- `max_value`: حداکثر مقدار برای حدس
- `assumed_number`: عدد فرضی برای حدس
و در خروجی یک لیست با شرایط زیر برگرداند:
- لیست شامل تمام اعداد حدس زده شده توسط تولیدکننده به ترتیب پیمایش باشد.
- بعد از هر حدس عجیب یک عضو `'!'` باشد.
یک حدس عجیب است اگر یکی از شرایط زیر را داشته باشد:
- اگر بعد از دریافت `G` از طرف پیمایشگر عددی بزرگتر یا مساوی حدس قبلی حدس زده شود، این یک حدس عجیب است!
- اگر بعد از دریافت `L` از طرف پیمایشگر عددی کوچکتر یا مساوی حدس قبلی حدس زده شود، این یک حدس عجیب است!
- اگر بعد از دریافت `E` از طرف پیمایشگر حدس زدن عدد ادامه پیدا کند، این یک حدس عجیب است!
- اگر عدد حدسی کمتر از `min_value` یا بیشتر از `max_value` باشد، این یک حدس عجیب است!
- اگر در کل فرایند پیمایش سه مرتبه حدس عجیب اتفاق افتاد، یک عضو `'!!!'` به لیست خروجی اضافه شود و پیمایش متوقف شود.
برای مثال:
```python
import generators
from source import guess_generator_iterator
gen = generators.guess_generator_1_correct
min_value, max_value, num = 1, 100, 50
lst = guess_generator_iterator(gen, min_value, max_value, num)
print(lst)
gen = generators.guess_generator_2_lazy
min_value, max_value, num = 1, 10, 7
lst = guess_generator_iterator(gen, min_value, max_value, num)
print(lst)
gen = generators.guess_generator_3_careless
min_value, max_value, num = 1, 30, 15
lst = guess_generator_iterator(gen, min_value, max_value, num)
print(lst)
gen = generators.guess_generator_4_stupid
min_value, max_value, num = 1, 10, 5
lst = guess_generator_iterator(gen, min_value, max_value, num)
print(lst)
```
خروجی نمونه بالا:
```python
[5, 15, 74, 71, 55, 49, 50]
[1, 2, 3, 4, 5, 6, 7]
[22, 2, 8, 9, 20, 18, 11, 15, 16, '!', 14, '!', 14, '!', '!!!']
[4, 3, '!', 2, '!', 1, '!', '!!!']
```
در این مثال تولیدکنندههای اول و دوم میتوانند به درستی عدد خواسته شده را حدس بزنند و هیچ حدس عجیبی هم نداریم؛ بنابراین حدسهای تولیدکننده به ترتیب داخل یک لیست خروجی داده میشود. در دو مثال بعدی هر کدام از تولیدکنندهها سه حدس عجیب دارند که بعد از هر کدام از آن حدسها یک `'!'` به لیست اضافه میشود و در آخر هم یک `'!!!'` به لیست اضافه میشود و حاصل برگردانده میشود.
# نکات
+ شما تنها مجاز به تغییر فایل `source.py` و تکمیل تابع
`guess_generator_iterator` هستید.
+ برای فرستادن کد صرفا فایل `source.py` را با فرمت `zip` فشرده کرده و بفرستید.
+ میتوانید کد تست نمونه را با استفاده از این [لینک](https://quera.ir/qbox/download/4eOWrvF1eb/chess_generator.zip) دانلود کنید.
# قسمت آموزشی
در این قسمت راهنماییهای سوال به ترتیب در روزهای شنبه، دوشنبه و چهارشنبه ساعت ۱۸ اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش ["سوال بپرسید"](https://quera.ir/contest/clarification/18303/) مطرح کنید.
<details class="blue">
<summary>راهنمایی ۱</summary>
در ابتدا برای حل این سوال شرطهای گفته شده در سوال برای «حدس فرضی» داده شده رو بررسی کنید و حدسهای بد و خوب رو مشخص و از هم جدا کنید.
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
نتیجه پیمایش تولیدکننده ورودی را درون یک متغیر قرار دهید. خروجی `yield` در تولیدکننده برابر مقداری است که شما از طریق تابع `send` متغیر ایجاد شده به تولیدکننده میفرستید. همینطور با استفاده از `try` و `except` میتوانید پایان یافتن پیمایش تولیدکننده را مدیریت کنید.
در کل این سئوال به توانایی خواندن داکیومنت پایتون هم ربط دارد، پیشنهاد میکنیم برای یادگیری بهتر حتما لینک اول سوال را بخوانید.
</details>
<details class="blue">
<summary>راهنمایی ۳</summary>
```python
numebr = 1
def myGenerator():
global numebr
while True:
status = (yield numebr)
if status == 'double':
numebr *= 2
else:
numebr += 1
def myFunction():
myGen = myGenerator()
current = next(myGen)
while True:
try:
print(current)
if current > 100:
myGen.close()
break
elif current%10 == 0:
current = myGen.send('double')
else:
current = myGen.send('single')
except:
break
myFunction()
```
خروجی کد بالا به صورت زیر میباشد:
```
1
2
3
4
5
6
7
8
9
10
20
40
80
160
```
</details>
تولیدکنندهی شطرنجی
--------------------------------------------------
میخواهیم تابعی بنویسیم که با استفاده از شیء گرایی یک شطرنج ساده پیادهسازی کند.
--------------------------------------------------
*کریم که چشمانش را خون گرفته از شدت سر رفتن حوصلهاش در دوران قرنطینه در حالی که قطعی اینترنت همچنان باقیست تصمیم نهایی خود مبنی بر پیادهسازی و ساخت برنامهی شطرنج گرفته است.*
او از شما میخواهد برایش با استفاده از پایتون شطرنج را پیادهسازی کنید.
# جزئیات
+ این بازی در جدولی که از هر طرف نامتناهیست انجام میشود. مهرهها به دو رنگ سفید و سیاه تقسیم شدهاند و برای هر رنگ یک مهرهی شاه و به تعداد نامتنهای مهرهی سرباز که میتوانند در صفحه حضور داشته باشند و میتوانند هم حضور نداشته باشند داریم.
+ در ابتدای بازی مهره شاه سفید در خانه $(-10 , -10)$ و شاه سیاه در خانه $(10 , 10)$ قرار دارد. و سربازی درون بازی وجود ندارد.
+ نحوهی پیروزی در این بازی به وسیلهی مات کردن حریف انجام میشود. مات کردن به این معنیست که حداقل یکی از مهرههای ما(به جز مهرهی شاه) در یکی از خانههای همسایهی راسی(هشت خانهی دور خانهی مورد نظر) خانهی شاه حریف باشد.
+ برای پیادهسازی باید از دو کلاس `Piece` و `Board` استفاده کنید که جزئیات آن را مشاهده میکنید:
## کلاس `Piece`
هر عضو این کلاس دارای سه ویژگی (*Attribute*) است که در تابع `ــinitــ` مقداردهی اولیه میشود و به ازای هر شی مقدار آن فرق دارد:
+ ۱. `sort`: که نشان دهندهی نوع مهرهی مورد نظر میباشد. این مقدار یا `"K"` به معنای شاه و یا `"P"` به معنای سرباز است.
+ ۲. `color`: که نشان دهندهی رنگ مهرهی مورد نظر میباشد. این مقدار یا `"black"` به معنای رنگ سیاه و یا `"white"` به معنای رنگ سفید است.
+ ۳. `position`: که نشان دهندهی جایگاه مهرهی مورد نظر در صفحه میباشد به طور مثال این ویژگی برای مهرهی شاه سفید رنگ $(-10 , -10)$ میباشد. نوع این متغیر *tuple* است.
## کلاس `Board`
هر عضو این کلاس دارای یک ویژگی (*Attribute*) است که در تابع `ــinitــ` مقداردهی اولیه میشود و به ازای هر شی مقدار آن فرق دارد:
+ `position`: که به صورت یک دیکشنری میباشد که قسمت `Key` در این دیکشنری به تمامی *position* های اشغال شده در صفحه تعلق دارد و قسمت `Value` برای هر *position* یک عنصر از کلاس `Piece` میباشد که در آن *position* جای گرفته است. توجه کنید که شما باید در تابع `__init__` این کلاس شاه سفید و سیاه را با شرایطی که در بالا گفته شد به دیکشنری اضافه کنید.
این کلاس دارای چهار متد زیر میباشد:
+ ۱. `add`: این متد به عنوان ورودی یک عنصر از کلاس `Piece` را میگیرد و آن را به صفحه اضافه میکند. توجه کنید که تنها باید یک شاه از هر رنگ در صفحه وجود داشته باشد و در *Position* مهرهای که به صفحه اضافه میکنیم نباید مهرهای وجود داشته باشد. اگر ورودی این متد در تناقض با این توضیحات بود متد باید عبارت `"invalid query"` را **چاپ** کند.
+ ۲. `remove`: این متد به عنوان ورودی یک *position* دریافت میکند و اگر در آن *position* مهرهای وجود داشت آن مهره را از صفحه پاک میکند. توجه کنید که حتما از هر رنگ دقیقا یک شاه باید درون صفحهی بازی قرار داشته باشد. اگر ورودی این متد در تناقض با این توضیحات بود و یا در *position* ورودی مهرهای وجود نداشت متد باید عبارت `"invalid query"` را **چاپ** کند.
+ ۳. `move`: این متد به عنوان ورودی یک شی از نوع `Piece` و یک *Position* که نشاندهنده جایگاه جدیدی است که این مهره در آن باید قرار بگیرد، دریافت میکند. در صورتی که در جایگاه فعلی این مهره در صفحه، همین مهره وجود داشته باشد و جایگاه جدید نیز خالی از مهره باشد، این مهره را به آن جایگاه منتقل میکنیم. اگر در جایگاه فعلی این مهره در صفحه همین مهره وجود داشته باشد و جایگاه جدید یکی از مهرههای سرباز حریف باشد، این مهره به آن جایگاه انتقال مییابد و مهرهی حریف از صفحه حذف میشود(چون حتما باید در هر لحظه از هر رنگ دقیقا یک مهره شاه وجود داشته باشد پس نمیتواند شاه حریف را مورد حمله قرار دهد). در غیر این صورت و یا اگر ورودی این متد در تناقض با این توضیحات بود متد باید عبارت `"invalid query"` را چاپ کند.
+ ۴. `is_mate`: این متد به عنوان ورودی یک رنگ ( `"white"` و یا `"black"`) را دریافت میکند و بررسی میکند که آیا مهرههای رنگ مورد نظر در وضعیت مات قرار دارد یا خیر. اگر قرار دارد مقدار `True` و اگر خیر مقدار `False` را برگرداند.
# نکات
+ نام فایل پایتون شما باید `solution.py`باشد.
+ میتوانید فایل اولیهی خام و کد تست نمونه را با استفاده از این [لینک](https://quera.ir/qbox/download/qx00lHAyi9/chess.zip) دانلود کنید.
# قسمت آموزشی
در این قسمت راهنماییهای سوال به ترتیب در روزهای شنبه، دوشنبه و چهارشنبه ساعت ۱۸ اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش ["سوال بپرسید"](https://quera.ir/contest/clarification/18303/) مطرح کنید.
<details class="blue">
<summary>راهنمایی ۱</summary>
مقداردهی در تابع `ــinitــ` به این صورت میباشد که برای هر شیء مقادیر مشخص و متفاوتی میتوانیم تعیین کنیم. به طور مثال نوشتن تابع `ــinitــ` برای کلاس *Piece* به صورت زیر میباشد:
``` python
def __init__(self, sort, color, position):
self.color = color
self.sort = sort
self.position = position
```
و یا برای نوشتن این تابع برای کلاس *Board* در ابتدا قبل از اضافه کردن شاهها به صفحه چون ویژگی `position ` یک دیکشنری خالیست باید به صورت زیر باشد:
``` python
def __init__(self):
self.position = {}
```
برای متد `add` در کلاس *Board* باید چک کنیم در جایگاهی که میخواهیم مهره را *add* کنیم آیا مهرهای وجود دارد یا خیر اگر وجود دارد *invalid query* چاپ کنیم وگرنه در ویژگی `position ` این *Board* باید این مهره اضافه شود.
برای متد `remove` در کلاس *Board* باید چک کنیم در این جایگاه مهرهای وجود دارد؟ و اگر وجود دارد این مهره شاه نباشد در غیر این صورت *invalid query* را چاپ کنیم در غیر این صورت مهرهای که در این جایگاه وجود دارد را از دیکشنری کلاس *Board* حذف کنیم.
برای متد `move` در کلاس *Board* باید چک کنیم اگر در *position* جدیدی که مهره باید به آنجا برود مهرهی همرنگ وجود داشت یا مهرهی شاه وجود داشت *invalid query* چاپ کند وگرنه اگر در جایگاه خالیای میرود جایگاه الان مهره از دیکشنری *Board* حذف شود و جایگاه جدید مهره و این مهره در دیکشنری *Board* اضافه شوند و اگر در جایگاهی که میرود مهرهی حریف وجود داشت هم این مهره و جایگاهش و هم مهرهی حریف و جایگاهش از دیکشنری *Board* حذف شوند و مهرهی حرکت داده شده و جایگاه جدیدش در دیکشنری *Board* اضافه شوند.
برای متد `is_mate` در کلاس *Board* نیازمند به چک کردن هر هشت خانهی دور شاه داریم تا بفهمیم که آیا حداقل یک مهره از مهرههای سرباز حریف در این خانهها وجود دارد یا خیر.
دقت کنید اگر خانهی شاه مورد نظر خانهی $(i , j)$ هشت خانهی دور آن خانههای زیر است:
$(i + 1 , j)$ ,
$(i + 1 , j + 1)$ ,
$(i + 1, j - 1)$ ,
$(i , j + 1)$ ,
$(i , j - 1)$ ,
$(i - 1 , j + 1)$ ,
$(i - 1, j)$ ,
$(i - 1, j - 1)$
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
برای متد `add` در کلاس *Board*شبه کد زیر همان ترجمهی راهنمایی یک به زبان پایتون است:
```
if piece.sort=="K" or piece.position in self.position:
print("invalid query")
else:
self.position[piece.position]=piece
```
برای متد `remove` در کلاس *Board* شبه کد زیر همان ترجمهی راهنمایی یک به زبان پایتون است:
```
if position not in self.position:
print("invalid query")
elif self.position[position].sort=="K":
print("invalid query")
else:
self.position.pop(position)
```
برای متد `move` در کلاس *Board* در راهنمایی ۳ شبه کد قرار میگیرد.
برای متد `is_mate` در کلاس *Board* در راهنمایی ۳ شبه کد قرار میگیرد.
</details>
<details class="blue">
<summary>راهنمایی ۳</summary>
برای متد `move` در کلاس *Board* شبه کد زیر همان ترجمهی راهنمایی یک به زبان پایتون است:
```
def move(self, piece, position2):
if position2 in self.position:
if self.position[position2].color == piece.color:
print("invalid query")
else:
if self.position[position2].sort == "K":
print("invalid query")
else:
del self.position[piece.position]
del self.position[position2]
self.position[position2] = piece
if piece.sort == "K":
if piece.color == "white":
global whitekingx, whitekingy, blackkingx, blackkingy
whitekingx = position2.x
whitekingy = position2.y
if piece.color == "black":
blackkingx = position2.x
blackkingy = position2.y
else:
del self.position[piece.position]
self.position[position2] = piece
```
برای متد `is_mate` در کلاس *Board* شبه کد زیر همان ترجمهی راهنمایی یک به زبان پایتون است:
```
def is_mate(self, color):
if(color == "white"):
for i in range(-1, 2):
for j in range(-1, 2):
if i != 0 or j != 0:
position = (whitekingx + i, whitekingy + i)
if position in self.position:
return True
return False
else:
for i in range(-1, 2):
for j in range(-1, 2):
if i != 0 or j != 0:
if (blackkingx + i, blackkingy + i) in self.position:
return True
return False
```
</details>
Chess
--------------------------------------------------
میخواهیم کمککنندهای (`helper`) بنویسیم که در موارد مشخص *Exception*ها را لاگ کند و از آن بگذرد.
--------------------------------------------------
*کریم در حال خواندن سوالات بود که از من به عنوان نویسندهی سوالات بابت توهینهایی که به کریم کردم در نداشتن توانایی یاد گرفتن برنامهنویسی دلخور شد. برای همین سوالی را طرح کرد که به من بفهماند که من هم توانایی یاد گرفتن برنامهنویسی را ندارم. از شما میخواهم به جای من سوال زیر را حل کنید.*
# جزئیات
سوال از این قرار است که باید در پایتون کمک کنندهای به نام `PyRanj` پیادهسازی کنید به صورتی که این کمککننده **سه قابلیت اصلی** زیر را داشته باشد:
## ۱. *Wrapper*
هر گاه از `pyranj` به عنوان *wrapper* یک تابع استفاده شود، در تابع مورد نظر نباید هیچ تغییری ایجاد شود و فقط در زمان صدا زدن تابع، در صورتی که *Exception* پرتاب شود، صرفا باید این اتفاق لاگ شود و برنامه ادامه پیدا کند. برای مثال در کد زیر باید خروجی پایینی لاگ شود:
```python
from pyranj import PyRanj as pyranj
@pyranj
def f():
raise Exception('Ranj')
f()
```
```
[EXCEPTION] :: Ranj
```
## ۲. *ContextManager*
هرگاه از `pyranj` به عنوان `context` استفاده شود، در عملیات مورد نظر نباید هیچ تغییری ایجاد شود و فقط اگر حین انجام عملیات، *Exception* پرتاب شود، صرفا باید این اتفاق لاگ شود و برنامه ادامه پیدا کند. برای مثال در کد زیر باید خروجی پایینی لاگ شود:
```python
from pyranj import PyRanj as pyranj
with pyranj:
raise Exception('Ranj')
```
```
[EXCEPTION] :: Ranj
```
## ۳. *Mixin*
هرگاه از `pyranj` در کلاسی ارثبری شود، **در صورتی که آن کلاس دارای متد run باشد**، تغییری که ایجاد میشود باید برابر با لاگشدن `Exception` با فرمت مشخص شده باشد. برای مثال در کد زیر باید خروجی پایینی لاگ شود:
```python
from pyranj import PyRanj as pyranj
class Runner(pyranj):
def run(self):
raise Exception('Ranj')
Runner().run()
```
```
[EXCEPTION] :: Ranj
```
----
**توجه:** برای لاگ کردن باید از متد `log` شی `logger` به شیوهی زیر عمل کنید و به آن یک رشته ورودی دهید (از [اینجا](https://quera.ir/qbox/download/sjEYu6PI2A/logger.py) میتوانید نمونه `logger` که در تستها استفاده میشود را دانلود کنید):
```python
from pyranj import PyRanj as pyranj
from logger import logger
logger.log('PyRanj Log')
```
علاوه بر قابلیتهای اصلی، کمککنندهی شما باید دارای قابلیتهای زیر نیز باشد.
### ۱. تغییر دادن `prefix` در متن لاگ
```python
@pyranj(prefix='[PREFIX]')
def f():
raise Exception('Ranj')
f()
```
```
[PREFIX] :: Ranj
```
**توجه کنید که این نوع از تغییر prefix در تمام ویژگیهای اصلی باید وجود داشته باشد.**
### ۲. لغو کردن `prefix` در متن لاگ
```python
from pyranj import PyRanj as pyranj
with pyranj(prefix='[PREFIX]')():
raise Exception('Ranj')
```
```
[EXCEPTION] :: Ranj
```
**توجه:** متنی که به ازای هر *Exception* لاگ میشود باید به فرمت زیر باشد، که در آن مقدار پیشفرض `prefix`، برابر است با `[EXCEPTION]`.
```python
f"{prefix} :: {exception}"
```
برای فهمیدن بهتر سوال میتوانید مثال زیر و خروجی آن را مشاهده کنید.
```python
from pyranj import PyRanj as pyranj
@pyranj
def f1():
raise Exception('x1')
f1()
pyrannnnnnnj = pyranj()()(prefix='Hey')()
@pyrannnnnnnj(prefix='You')
def f2():
raise Exception('x2')
f2()
with pyranj(prefix='Yes')()()()()():
raise Exception('x3')
class A(pyranj):
def run(self, num):
raise Exception('x' * num)
A().run(5)
class B(pyranj()()(prefix='Hey there is an error')):
def run(self):
raise Exception('run ...')
B().run()
```
```
[EXCEPTION] :: x1
You :: x2
[EXCEPTION] :: x3
[EXCEPTION] :: xxxxx
Hey there is an error :: run ...
```
# نکات
+ نام برنامهی ارسالی شما باید `pyranj.py` باشد که در آن شی `PyRanj` وجود داشته باشد و ویژگیهای گفته شده را داشته باشد.
+ میتوانید کد تست نمونه را با استفاده از این [لینک](https://quera.ir/qbox/download/lSEP2gEP6T/pyranj_sampletest.zip) دانلود کنید.
+ تستهای اصلی این سوال از ۵ بخش تشکیل شده که هر بخش دارای سه نوع تست است؛ یعنی هنگام ارسال شما نام هر متد تست را ۵ بارمیبینید. این بخشها به ترتیب دستههای زیر هستند:
+ بخش `BasePyRanjTest`
+ بخش `TestPyRanjInstantiation`
+ بخش `TestPyRanjInstantiationWithPrefix`
+ بخش `TestPyRanjRecursiveInstantiation`
+ بخش `TestPyRanjRecursiveInstantiationWithPrefix`
# قسمت آموزشی
در این قسمت راهنماییهای سوال به ترتیب در روزهای شنبه، دوشنبه و چهارشنبه ساعت ۱۸ اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش ["سوال بپرسید"](https://quera.ir/contest/clarification/18303/) مطرح کنید.
<details class="blue">
<summary>راهنمایی ۱</summary>
توصیه میشود ابتدا در مورد مفاهیم
wrapper, contextManager, Mixin
از این لینکها بخوانید:
+ https://wiki.python.org/moin/FunctionWrappers
+ https://book.pythontips.com/en/latest/context_managers.html
+ https://www.ianlewis.org/en/mixins-and-python
توجه کنید که `helper` مورد نظر لزومی ندارد به صورت عادی تعریف شود و میتواند خودش حاصل فراخوانی یک تابع دیگر باشد.
برای این که `helper` مورد نظر به طور همزمان قابل ارثبری باشد و همچنین قابل فراخوانی، میتوان از تابع `__call__` در `metaclass` کمک گرفت. برای اطلاع ازین مورد میتوانید لینک زیر را بخوانید:
+ https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Metaprogramming.html
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
ابتدا یک decorator عادی تعریف کنید که یک prefix ورودی میگیرد و یک تابع را تغییر میدهد.
سپس تابعی تعریف کنید به اسم get_pyranj که یک prefix هم ورودی میگیرد.
اینگونه در هرجایی از class یا metaclass به آن prefix دسترسی دارید.
درون تابع metaclass و class مربوطه را تعریف کنید و در خروجی آن class مورد نظر را برگردانید.
توابع __enter__ و __exit__ را در metaclass بیفزایید (در تابع exit میتوانید بفهمید آیا در طول اجرا با این کانتکست Exception پرتاب شده است یا نه.
حالا برای اینکه حالت ارثبری را به درستی هندل کنید، متود __getattribute__ را در class باز نویسی کنید. و در صورتی که attribute مورد نظر اسمش برابر با run بود آن را دکوریت کنید.
اگر توجه داشته باشید موقع فراخوانی این helper سه حالت ممکن است پیش بیاید (ساخته شدن یک آبجکت جدید که از helper ارث بری کرده است، دکوریت کردن یک تابع، و تحویل گرفتن دکوریتور با یک prefix به عنوان ورودی) که میتوان در متود __call___ در metaclass آنها را هندل کرد
```python
def pyranj_message(e, prefix=None):
...
def pyranj_decorator(prefix=None):
...
def get_pyranj(prefix_=None):
class InnerPyRanjMeta(type):
def __call__(cls, *args, **kwargs):
...
# We may call get_pyranj recursively
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
...
class InnerPyRanj(metaclass=InnerPyRanjMeta):
def __getattribute__(self, item):
...
return InnerPyRanj
PyRanj = get_pyranj()
```
</details>
<details class="blue">
<summary>راهنمایی ۳</summary>
کد نهایی سوال به شکل زیر میشود:
```python
from logger import logger
PREFIX = 'prefix'
FUNC = 'func'
RUN = 'run'
def pyranj_message(e, prefix=None):
if prefix is None:
prefix = '[EXCEPTION]'
return f'{prefix} :: {e}'
def pyranj_decorator(prefix=None):
def decorator(f):
def wrapper(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
logger.log(pyranj_message(e, prefix))
return wrapper
return decorator
def get_pyranj(prefix_=None):
class InnerPyRanjMeta(type):
def __call__(cls, *args, **kwargs):
if cls != InnerPyRanj:
return super(InnerPyRanjMeta, cls).__call__(*args, **kwargs)
param = args[0] if args else None
func = param if type(param) == type(pyranj_decorator) else None
prefix = kwargs.get(PREFIX, None)
if func:
return pyranj_decorator(prefix_)(func)
return get_pyranj(prefix)
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
logger.log(pyranj_message(exc_val, prefix=prefix_))
return True
class InnerPyRanj(metaclass=InnerPyRanjMeta):
def __getattribute__(self, item):
attr = super().__getattribute__(item)
if item == RUN:
return pyranj_decorator(prefix_)(attr)
return attr
return InnerPyRanj
PyRanj = get_pyranj()
```
</details>