فحص صفوف اللوحة لتحديد الفائز (row_winner)
حللت حتى الآن تمارين قصيرة، كل واحد منها يركز على مشكلة صغيرة. الآن سنبني مشروعًا أكبر وأكثر تعقيدًا يختبر ما تعلمته ويجمع عدة أجزاء صغيرة معًا.
ستطوّر لعبة Tic-Tac-Toe تفاعلية نصية يلعبها شخصان. هذا مثال صغير لشكل اللعبة أثناء اللعب:
1 2 3 1 | | -+-+- 2 | | -+-+- 3 | |
X to play: 1 1
1 2 3 1 X| | -+-+- 2 | | -+-+- 3 | |
O to play: 2 2
1 2 3 1 X| | -+-+- 2 |O| -+-+- 3 | |
X to play: 1 3
1 2 3 1 X| |X -+-+- 2 |O| -+-+- 3 | | سنقسّم المشروع إلى عدة دوال صغيرة، وكل دالة ستكون تمرينًا مستقلًا.
ستستخدم مفاهيم كثيرة تعلمتها بالفعل: النصوص، القوائم المتداخلة، الحلقات المتداخلة، range، استدعاء الدوال من داخل دوال أخرى، المقارنات، والقيم المنطقية. وأثناء المشروع ستتعلم أيضًا مفاهيم جديدة مثل محرف السطر الجديد، الأنواع، والدالة input().
الخطة العامة للمشروع:
- ثلاث دوال
row_winnerوcolumn_winnerوdiagonal_winnerلفحص الصفوف والأعمدة والأقطار الفائزة. - دالة
winnerتجمع هذه الفحوص لتحديد وجود فائز في اللوحة كلها. - دالة
format_boardتعرض حالة اللوحة الحالية. - دالة
play_moveتستقبل إدخال المستخدم لتنفيذ حركة. - وأخيرًا دالة
play_gameتجمع كل شيء وتشغل اللعبة بصورة تفاعلية. - وبعد ذلك يمكن إضافة تحسينات أخرى.
لنبدأ.
كما في الفصل السابق، سنمثل لوحة اللعبة بقائمة متداخلة من النصوص. في اللعبة المعتادة تكون اللوحة 3×3، أي ثلاث قوائم، في كل واحدة ثلاثة نصوص. نمثل اللاعبين بـ'X' أو 'O'، والمربع الفارغ بمسافة ' '. مثال:
board = [ ['X', 'O', 'X'], ['O', ' ', ' '], [' ', 'X', 'O'] ] ولجعل المسألة أكثر فائدة، يجب أن يعمل كودك مع لوحات مربعة بأي حجم مثل 4×4 أو 5×5، ويمكن أن تكون رموز اللاعبين أي نصوص، مثل:
board = [
['A', 'B', 'A', 'A'],
['B', ' ', ' ', 'A'],
[' ', 'A', 'B', 'B'],
[' ', 'A', 'B', ' ']
]
اكتب دالة row_winner تعيد True إذا كانت board تحتوي على صف فائز: خط أفقي تكون جميع خاناته الرمز نفسه، بشرط ألا يكون هذا الرمز هو المسافة ' ':
def row_winner(board):
...
check_result(
row_winner(
[
['A', 'A', 'B', 'A'],
[' ', ' ', ' ', ' '],
['A', ' ', ' ', 'A'],
['B', ' ', 'B', 'A']
]
),
False
)
check_result(
row_winner(
[
['X', ' ', 'X'],
['O', 'X', 'X'],
['O', 'O', 'O']
]
),
True
)
في المثال الثاني يفوز O في الصف الأخير.
You need to check every row in the board, so you'll need a loop for that.
How can you check if all entries in a row are equal to each other?
That's a self contained problem on its own. You can start by forgetting about the whole board and just checking a single row.
You could even write a function which just does this, although you don't have to.
Since the row could have any size, you'll have to loop all the way through it.
For each row, define a boolean. Then loop through that row, updating the boolean accordingly.
You can use the first entry
row[0]in a row to compare all the row entries to it.Think carefully about what the initial value of the boolean should be, and under what conditions you should change its value.
After looping through a row, if you determined that all its entries are equal, then return
True(ending the outer loop early).Make sure you don't return
Truefor a row filled with spaces.Make sure you return
Falseif there are no winning rows
أحسنت!
اكتب الآن دالة مشابهة اسمها column_winner تتحقق من وجود عمود فائز، أي خط رأسي يحتوي على الرمز نفسه في جميع خاناته:
def column_winner(board):
...
check_result(
column_winner(
[
['X', 'O', ' '],
['X', 'O', ' '],
['O', 'X', ' ']
]
),
False
)
check_result(
column_winner(
[
['X', 'O', ' ', 'X'],
[' ', 'O', 'X', 'O'],
['O', 'O', 'X', 'X'],
['O', 'O', 'X', ' ']
]
),
True
)
في اللوحة الثانية فاز O في العمود الثاني.
لا يمكنك المرور على الأعمدة مباشرة بالطريقة نفسها التي تمر بها على الصفوف. العمود الأول مثلًا يتكوّن من العنصر الأول في كل صف، والعمود الثاني من العنصر الثاني في كل صف، وهكذا. لذلك ستحتاج إلى المرور على أرقام مواضع الأعمدة، ثم المرور على الصفوف للحصول على العنصر الموافق للعمود الحالي. بعد ذلك طبّق منطق التحقق نفسه الذي استخدمته مع الصفوف، مع الانتباه إلى ' '.
You can start by imitating
row_winnerabove, then change it to make it work with columns.You can't loop through the columns of
boardas simply as its rows.What is a column of a nested list? The first column consists of the first element of the first row, the first element of the second row, etc.
Looping through all columns means looking at the first element of every row, then the second element of every row, etc.
So you need to loop through numbers representing the positions first, second, etc.
How do you find the number of columns in
board?That covers the outer loop, which goes through each column. Then you need an inner loop to go through each element in the column.
The different entries of a column are NOT on the same row. So how can you access them?
You can loop through rows of the board and find the element corresponding to that row and the current column.
To access all the entries of, say, the 5th column, you can loop through all the rows, and access the 5th element in each row.
Define a boolean for each column, then update it accordingly inside the inner loop.
The rest of the logic is very similar to
row_winner.Watch out for
' '.Remember to return
Falseat the end if needed.
ممتاز! كان ذلك تحديًا جيدًا.
بقي التحقق من الأقطار الفائزة. في الفصل السابق كتبت دالة تعمل مع لوحة 3×3:
def diagonal_winner(board):
middle = board[1][1]
return (
(middle == board[0][0] and middle == board[2][2]) or
(middle == board[0][2] and middle == board[2][0])
)
اكتب الآن diagonal_winner تعمل مع لوحة مربعة بأي حجم: 4×4 أو 5×5 وهكذا:
def diagonal_winner(board):
...
check_result(
diagonal_winner(
[
['O', 'X', 'O', 'X'],
[' ', 'O', 'X', ' '],
['X', 'X', ' ', 'X'],
['X', ' ', 'O', 'O']
]
),
True
)
check_result(
diagonal_winner(
[
['X', 'X', ' '],
['X', ' ', 'O'],
[' ', 'O', 'O']
]
),
False
)
في المثال الأول فاز X على القطر الممتد من أسفل اليسار إلى أعلى اليمين.
عدد الأقطار الرئيسية يظل اثنين مهما كان حجم اللوحة. ابحث عن نمط الفهارس في القطر من أعلى اليسار إلى أسفل اليمين، ثم ابحث عن نمط القطر الآخر. يمكن أن تساعدك الفهرسة السالبة في القطر الثاني. بعد ذلك تحقق من أن جميع القيم على القطر متساوية وغير فارغة.
How many diagonals are there on a square board of arbitrary size?
Even if the size of the board changes, the number of diagonals remains the same!
You can't do something like
middle == board[0][0] and middle == board[2][2]this time, because you don't know how long a diagonal is.Moreover the two diagonals might not have anything in common like
middle.First, focus on the diagonal that goes from top left to bottom right.
How can you access those entries with double subscripting?
Do you see a pattern in those double subscripts? Get some paper and pen, work it out on some examples.
Now focus on the other diagonal (from top right to bottom left). There is a pattern in the subscripts again, but it's a little bit more difficult.
Do you remember negative indexing? It might be helpful here.
Once you get the hang of the patterns, use the same ideas from before to check if all entries are equal.
You can use one loop and check both diagonals at the same time. Or you can use one loop for each diagonal.
رائع! الآن نستطيع جمع الدوال الثلاث معًا.
اكتب دالة winner تستقبل board وتعيد True إذا كانت اللوحة تحتوي على صف فائز أو عمود فائز أو قطر فائز، وتعيد False في غير ذلك.
يجب أن يعتمد حل winner على استدعاء الدوال الثلاث. لا تجعل winner نفسها تنفذ حلقات أو فهرسة أو تعيد تنفيذ المنطق من الصفر.
إليك تنفيذات جاهزة لـrow_winner وcolumn_winner وdiagonal_winner مع اختبارات لـwinner. انسخ الكود وأكمل الفراغ فقط:
def winner(board):
...
def winning_line(strings):
piece = strings[0]
if piece == ' ':
return False
for entry in strings:
if piece != entry:
return False
return True
def row_winner(board):
for row in board:
if winning_line(row):
return True
return False
def column_winner(board):
for col in range(len(board[0])):
column = []
for row in board:
column.append(row[col])
if winning_line(column):
return True
return False
def diagonal_winner(board):
diagonal1 = []
diagonal2 = []
for i in range(len(board)):
diagonal1.append(board[i][i])
diagonal2.append(board[i][-i-1])
return winning_line(diagonal1) or winning_line(diagonal2)
check_result(
winner(
[
['X', 'X', 'X', ' '],
['X', 'X', ' ', ' '],
['X', ' ', 'O', 'X'],
[' ', ' ', 'O', 'X']
]
),
False
)
check_result(
winner(
[
['X', ' ', 'X'],
['O', 'X', 'O'],
['O', 'O', 'O']
]
),
True
)
check_result(
winner(
[
['X', ' '],
['X', 'O']
]
),
True
)
الحل قصير جدًا إذا استخدمت الدوال الثلاث والعامل المنطقي المناسب.
The solution is quite short! Simply use the three functions correctly.
Think about possible cases. When does
winner(board)returnFalse? When does it returnTrue?How can you use the three functions and a boolean operator together to get the result you need?
عمل رائع!
أصبح لدينا الآن الكود الذي يحدد ما إذا كانت اللوحة تحتوي حالة فوز.