پروژههای ترم اول
پایان ترم اول. در این فصل مطلب تازهای یاد نمیگیرید؛ همه چیزهایی که خواندهاید را در پنج برنامه کامل کنار هم میگذاریم. هر پنج پروژه فقط از ابزارهای فصل یک تا نوزده استفاده میکنند.
۱. چطور یک پروژه را شروع کنیم
بزرگترین مشکل تازهکارها این نیست که پایتون بلد نیستند؛ این است که نمیدانند از کجا شروع کنند. روش کار همیشه همین است:
- بازی را روی کاغذ توضیح دهید. اگر نمیتوانید به فارسی بگویید چه اتفاقی میافتد، نمیتوانید به پایتون هم بگویید.
- دادهها را مشخص کنید. چه چیزهایی را باید نگه دارید؟ امتیاز، صفحه بازی، لیست کلمات؟ نوع ظرفشان را انتخاب کنید.
- کارها را به تابع بشکنید. «صفحه را نشان بده»، «حرکت بگیر»، «برنده را پیدا کن».
- سادهترین نسخه کارکننده را بنویسید. بدون امتیاز، بدون مرحله، بدون زیبایی.
- بعد کامل کنید. اعتبارسنجی ورودی، پیامهای بهتر، ذخیره در فایل.
۲. پروژه یک: حدس عدد با مرحله و جان
نسخه پیشرفته بازی فصل هشتم: چند مرحله، تعدادی جان، و همه متنها در یک ماژول تنظیمات.
'''All the texts and the levels of the game.'''
LEVELS = [
{
'level_number': 1,
'number': 2,
'hint': 'A number between 1 and 3: ',
},
{
'level_number': 2,
'number': -7,
'hint': "A negative number greater than -10: ",
},
{
'level_number': 3,
'number': 42,
'hint': 'A number between 1 and 100: ',
},
]
START_HEARTS = 5
MESSAGE_WIN = 'You won the level!'
MESSAGE_GREATER = 'The secret number is greater!'
MESSAGE_LOWER = 'The secret number is lower!'
MESSAGE_GAMEOVER = 'Game over!'
MESSAGE_FINISHED = 'There are no more levels right now!'
فایل constants.py: پروژه 01_number_guess
def play_level(level: dict, hearts: int) -> int:
'''Play one level.
Returns the number of hearts left; 0 means the player lost.
'''
secret = level['number']
while hearts > 0:
guess = read_number(level['hint'])
if guess == secret:
print(MESSAGE_WIN)
hearts += 1 # a bonus life for winning the level
show_hearts(hearts)
return hearts
if guess > secret:
print(MESSAGE_LOWER)
else:
print(MESSAGE_GREATER)
hearts -= 1
show_hearts(hearts)
return 0
def main() -> None:
hearts = START_HEARTS
show_hearts(hearts)
for level in LEVELS:
print(f"\n--- Level {level['level_number']} ---")
hearts = play_level(level, hearts)
if hearts == 0:
print(MESSAGE_GAMEOVER)
return # leave the game at once
print(MESSAGE_FINISHED)
منطق بازی: پروژه 01_number_guess
شرح پروژه
- مرحلهها یک لیستی از دیکشنریها هستند، همان ساختار فصل پانزدهم. برای اضافه کردن مرحله چهارم فقط چند خط داده اضافه میشود و هیچ کدی عوض نمیشود.
- تابع
play_levelتعداد جان باقیمانده را برمیگرداند وmainآن را در متغیر خودش نگه میدارد. هیچglobalدر کار نیست. - صفر برگرداندن یعنی باخت.
mainبا دیدن صفر، بلافاصلهreturnمیکند.
نسخه اولیه سه مشکل داشت: از global hearts استفاده
میکرد، بعد از تمام شدن جانها بازی ادامه پیدا میکرد
و به مرحله بعد میرفت، و پیام «همه مراحل را تمام کردید» را
همیشه چاپ میکرد، حتی وقتی بازیکن باخته بود. هر سه
با برگرداندن مقدار به جای متغیر سراسری حل شدند.
۳. پروژه دو: سنگ کاغذ قیچی
'''Rock - paper - scissors, first player to three points wins.'''
import random
ROCK = 'rock'
PAPER = 'paper'
SCISSORS = 'scissors'
MOVES = [ROCK, PAPER, SCISSORS]
# BEATS[a] is the move that 'a' defeats. One dictionary replaces six elif lines.
BEATS = {
ROCK: SCISSORS,
PAPER: ROCK,
SCISSORS: PAPER,
}
POINTS_TO_WIN = 3
def read_move(player: str) -> str:
'Ask a player for a valid move (or "exit").'
while True:
move = input(f'{player}, enter your move (rock/paper/scissors): ').lower().strip()
if move == 'exit' or move in MOVES:
return move
print('Unknown move, try again.')
def winner_of(move1: str, move2: str) -> int:
'''Return 0 for a draw, 1 when move1 wins, 2 when move2 wins.'''
if move1 == move2:
return 0
if BEATS[move1] == move2:
return 1
return 2
قواعد بازی: پروژه 02_rock_paper_scissors
شرح پروژه
- ستاره این پروژه، دیکشنری
BEATSاست. بدون آن باید شش شاخهelifبنویسید. با آن، کل قاعده بازی سه خط داده است. - این یک درس مهم برنامهنویسی است: داده به جای کد. اگر بخواهید بازی را به نسخه پنجحالته گسترش دهید، فقط دیکشنری را بزرگتر میکنید.
- تابع
winner_ofعدد برمیگرداند و هیچ چاپی ندارد. پس میشود جداگانه آزمایشش کرد و در نسخه گرافیکی هم به کار برد. - ترکیب
.lower().strip()روی ورودی، حرف بزرگ و فاصله اضافه را خنثی میکند.
۴. پروژه سه: وردل
def mask_word(word: str, found_letters: set[str]) -> str:
'Show the letters already found and hide the others behind "_".'
masked = ''
for letter in word:
if letter in found_letters:
masked += letter
else:
masked += '_'
masked += ' '
return masked.strip()
def read_letter(already_tried: set[str]) -> str:
'Ask for one single letter that was not tried before.'
while True:
letter = input('Enter a letter: ').lower().strip()
if len(letter) != 1:
print('You can only enter one letter!')
continue
if not letter.isalpha():
print('Letters only, please.')
continue
if letter in already_tried:
print('You already tried that one.')
continue
return letter
نمایش و ورودی: پروژه 03_wordle
def play(word: str) -> bool:
'Play one word. Return True when the player found it.'
found_letters: set[str] = set()
tried_letters: set[str] = set()
wrong_guesses = 0
print(f'\nThe word has {len(word)} letters!')
print(mask_word(word, found_letters))
while wrong_guesses < MAX_WRONG_GUESSES:
letter = read_letter(tried_letters)
tried_letters.add(letter)
if letter in word:
found_letters.add(letter)
print('Yes!')
else:
wrong_guesses += 1
print(mask_word(word, found_letters))
# The word is complete when every one of its letters has been found.
if set(word) == set(word) & found_letters:
print(f'You win! The word was {word.capitalize()}.')
return True
print(f'You lost. The word was {word.capitalize()}.')
return False
حلقه بازی: پروژه 03_wordle
The word has 6 letters! _ _ _ _ _ _ Enter a letter: a Yes! _ a _ _ _ _ Enter a letter: o Yes! _ a _ o o _
شرح پروژه
- دو مجموعه استفاده شده است: حروف پیدا شده و حروف امتحانشده. مجموعه انتخاب درستی است چون تکرار در آن معنا ندارد و بررسی عضویتش سریع است.
- شرط برد با عملیات مجموعهای فصل چهاردهم نوشته شده: اگر همه حروف کلمه در مجموعه پیدا شدهها باشند، بازیکن برده است.
- تابع
read_letterنمونه کامل اعتبارسنجی است: باید یک کاراکتر باشد، باید حرف باشد، و نباید تکراری باشد.
در نسخه اولیه، کاربر میتوانست به جای یک حرف، یک کلمه کامل وارد کند و اگر آن کلمه بخشی از پاسخ بود، برنده اعلام میشد. بهعلاوه محدودیتی برای تعداد حدسهای غلط وجود نداشت و راهی برای خروج از بازی هم نبود. هر سه اصلاح شدهاند.
۵. پروژه چهار: دوز
PLAYER_SIGN = 'X'
AI_SIGN = 'O'
WINNING_LINES = [
(0, 1, 2), (3, 4, 5), (6, 7, 8), # rows
(0, 3, 6), (1, 4, 7), (2, 5, 8), # columns
(0, 4, 8), (2, 4, 6), # diagonals
]
def new_board() -> list[str]:
'Return a fresh board where every cell shows its own number.'
return ['1', '2', '3', '4', '5', '6', '7', '8', '9']
def show_board(board: list[str]) -> None:
'Draw the board.'
print()
print(f' {board[0]} | {board[1]} | {board[2]} ')
print('---+---+---')
print(f' {board[3]} | {board[4]} | {board[5]} ')
print('---+---+---')
print(f' {board[6]} | {board[7]} | {board[8]} ')
print()
def is_legal(board: list[str], move: str) -> bool:
'A move is legal when it is 1-9 and the cell is still free.'
if not move.isdigit():
return False
number = int(move)
if number < 1 or number > 9:
return False
return board[number - 1] not in (PLAYER_SIGN, AI_SIGN)
صفحه و قواعد: پروژه 04_tic_tac_toe
def convert_persian_digits(text: str) -> str:
'Turn Persian digits into English ones so both keyboards work.'
persian_digits = '۰۱۲۳۴۵۶۷۸۹'
english_digits = '0123456789'
table = str.maketrans(persian_digits, english_digits)
return text.translate(table)
پشتیبانی از کیبورد فارسی: پروژه 04_tic_tac_toe
شرح پروژه
- صفحه یک لیست نهتایی است، نه ماتریس سه در سه. کار با اندیس سادهتر میشود و شماره خانهها برای کاربر طبیعی است.
- هشت خط برنده در یک لیست از تاپلها ذخیره شده است. بررسی برد فقط یک حلقه روی همین لیست است. باز هم داده به جای کد.
- خانههای خالی شماره خودشان را نشان میدهند، پس بازیکن ضرورتی ندارد شمارهگذاری را حفظ کند.
- تابع تبدیل ارقام فارسی نکته خوبی است: کاربر ایرانی ممکن است با کیبورد فارسی عدد بزند و برنامه باید هر دو را بفهمد.
- هوش مصنوعی این نسخه ساده است و فقط خانه تصادفی میگیرد. یکی از تمرینهای فصل این است که باهوشش کنید.
ورودی غیرعددی باعث ValueError و خراب شدن برنامه
میشد، امتیازها در متغیرهای سراسری نگه داشته میشدند، و برای خروج
از sys.exit() استفاده شده بود که برنامه را ناگهانی
قطع میکند. نسخه اصلاحشده با isdigit() بررسی میکند و
با return تمیز بیرون میآید.
۶. پروژه پنج: بازی آزمون
این پروژه تازهای است که در نسخه اصلی دوره وجود نداشت و برای تمرین فصل هجدهم اضافه شده است: سؤالها در فایل متنی هستند، نه داخل کد.
Which keyword defines a function in Python?|def|class|func|define
What does len('Kadoos') return?|6|5|7|error
Which type is the result of input()?|str|int|float|bool
قالب فایل questions.txt: سؤال، پاسخ درست، سپس گزینههای غلط
def load_questions(path: str) -> list[dict]:
'''Read the file and return a list of questions.
Every question is a dictionary: {'text': ..., 'correct': ..., 'options': [...]}
'''
if not os.path.exists(path):
raise FileNotFoundError(f'{path} was not found')
questions: list[dict] = []
with open(path, encoding='utf-8') as f:
for line_number, line in enumerate(f, start=1):
line = line.strip()
if line == '':
continue
parts = line.split('|')
if len(parts) < 3:
print(f'[warning] line {line_number} is ignored (wrong format)')
continue
text = parts[0]
correct = parts[1]
options = parts[1:]
random.shuffle(options)
questions.append({
'text': text,
'correct': correct,
'options': options,
})
return questions
خواندن سؤالها از فایل: پروژه 05_quiz
شرح پروژه
- سؤالها بیرون از کد هستند. معلم میتواند سؤال اضافه کند بدون اینکه یک خط پایتون بلد باشد. این یکی از مهمترین اصول طراحی نرمافزار است.
- هر خط با
split('|')شکسته میشود. اولین تکه متن سؤال است، دومی پاسخ درست، و بقیه گزینههای غلط. - خطهای خراب نادیده گرفته میشوند با یک هشدار، به جای اینکه کل برنامه را از کار بیندازند. شماره خط هم چاپ میشود تا معلم بتواند اصلاحش کند.
- گزینهها بُر زده میشوند تا پاسخ درست همیشه گزینه اول نباشد.
load_questionsخودش خطا میاندازد وmainآن را میگیرد؛ همان تقسیم مسئولیت فصل نوزدهم.
۷. درسهای مشترک این پنج پروژه
اگر هر پنج پروژه را بخوانید، الگوهای یکسانی میبینید. این موارد همان چیزی است که از ترم اول باید با خودتان ببرید:
| الگو | توضیح |
|---|---|
| ثابتها در بالای فایل | با حروف بزرگ. برای تغییر تنظیمات، کد را نمیگردید |
| داده به جای کد | دیکشنری BEATS، لیست WINNING_LINES، لیست LEVELS |
| هر تابع یک کار | خواندن، محاسبه، نمایش، جدا از هم |
| توابع بدون چاپ | تابعی که فقط مقدار برمیگرداند، همهجا قابل استفاده است |
| اعتبارسنجی هر ورودی | حلقه تا وقتی جواب معتبر شود |
| راه خروج | هر بازی باید گزینه exit داشته باشد |
main() و نگهبان | در هر پنج پروژه |
بدون global | مقدار را برگردانید، متغیر سراسری نسازید |
پیش از خواندن کد هر پروژه، خودتان یک نسخه سادهاش را بنویسید. بعد کد این فصل را بخوانید و مقایسه کنید. تفاوتها را یادداشت کنید. این کار ده برابر بیشتر از خواندن صرف به شما یاد میدهد.
پوشه S20-term1-projects شامل پنج پوشه پروژه:
01_number_guess، 02_rock_paper_scissors،
03_wordle، 04_tic_tac_toe و
05_quiz. هر کدام را از داخل پوشه خودش با
python app.py اجرا کنید.
پایان ترم اول
تا اینجا شما میتوانید هر برنامه متنیای را بنویسید: داده بگیرید، پردازش کنید، در فایل ذخیره کنید و خطاها را مدیریت کنید. این پایه محکمی است.
ترم دوم از فصل بیست و یکم شروع میشود و کاملاً متفاوت است: به جای نوشتن دستورها، یاد میگیرید مدل بسازید. شیءگرایی، پایگاه داده، رابط گرافیکی و وب. اگر مطالب ترم اول را خوب بلد نیستید، پیش از رفتن به فصل بعد یک بار دیگر تمرینهای فصلهای یازده تا نوزده را انجام دهید.
تمرینهای فصل بیست
حدس عدد
- بهترین رکورد (کمترین تعداد حدس) را در فایل ذخیره کنید و در شروع هر بازی نشانش دهید.
- بازی را برعکس کنید: شما عددی در ذهن بگیرید و کامپیوتر حدس بزند و بپرسد «بیشتر یا کمتر». برای بازه ۱ تا ۱۰۰ چند حدس لازم دارد و چرا؟
- مرحلهای اضافه کنید که بازه ۱ تا ۱۰۰۰ باشد ولی فقط ده حدس داشته باشید.
سنگ کاغذ قیچی
- به جای یک دور، سه برد از پنج بازی کنید.
- بشمارید بازیکن بیشتر چه چیزی میزند و کامپیوتر از آن استفاده کند. آیا شروع به بردن میکند؟
- حالتهای «مارمولک» و «اسپاک» را اضافه کنید. قواعد را در دیکشنری بگذارید، نه در زنجیره
if.
وردل
- فهرست کلمات را به جای لیست پایتونی، از فایل بخوانید.
- حروف امتحانشده را نشان دهید و از تکرارشان جلوگیری کنید.
- یک وردل واقعی بسازید: پنج حرف، شش تلاش، و برای هر حرف یک علامت (درست و در جای درست، درست ولی جای غلط، اصلاً نیست).
دوز
- حالت دو نفره روی یک کیبورد اضافه کنید.
- کامپیوتر را باهوش کنید: اگر حرکت برنده دارد بزند، وگرنه جلوی برد حریف را بگیرد.
- امتیاز چند بازی را نگه دارید و در پایان نشان دهید.
بازی آزمون
- زمانسنج اضافه کنید: ده ثانیه برای هر سؤال.
- به سؤالها دستهبندی بدهید و بگذارید بازیکن انتخاب کند.
- نتیجه هر بازی را در فایل بنویسید و صفحه «پنج نفر برتر» بسازید.
مشترک بین پروژهها
- چیزهای مشترک هر پنج برنامه (پاک کردن صفحه، خواندن امن عدد، انتظار برای اینتر) را در ماژول
helpers.pyبگذارید. - یک
menu.pyبنویسید که پنج بازی را نشان دهد و بازی انتخابشده را اجرا کند. - کد خودتان را مرور کنید و مطمئن شوید هر برنامه در برابر جواب غلط، خط خالی و
Ctrl + Cمقاوم است.