فحص صفوف اللوحة لتحديد الفائز (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 في الصف الأخير.
تحتاج إلى فحص كل صف في اللوحة، لذلك ستحتاج إلى حلقة.
كيف تتحقق أن كل عناصر الصف متساوية؟
هذه مسألة مستقلة بذاتها. ابدأ بتجاهل اللوحة كلها وفحص صف واحد فقط.
يمكنك حتى كتابة دالة تفعل ذلك وحدها، لكن هذا ليس إلزاميًا.
لأن طول الصف قد يختلف، ستحتاج إلى المرور على كل عناصره.
لكل صف أنشئ متغيرًا منطقيًا، ثم مر على الصف وحدث قيمته حسب الحاجة.
يمكنك استخدام العنصر الأول
row[0]لمقارنة بقية عناصر الصف به.فكر جيدًا في القيمة الابتدائية للمتغير المنطقي، ومتى يجب تغييرها.
بعد المرور على الصف، إذا ثبت أن كل عناصره متساوية فأعد
True، وبذلك تنهي الحلقة الخارجية مبكرًا.تأكد أنك لا تعيد
Trueلصف مملوء بالمسافات.تأكد أنك تعيد
Falseإذا لم يوجد أي صف فائز.
أحسنت!
اكتب الآن دالة مشابهة اسمها 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 في العمود الثاني.
لا يمكنك المرور على الأعمدة مباشرة بالطريقة نفسها التي تمر بها على الصفوف. العمود الأول مثلًا يتكوّن من العنصر الأول في كل صف، والعمود الثاني من العنصر الثاني في كل صف، وهكذا. لذلك ستحتاج إلى المرور على أرقام مواضع الأعمدة، ثم المرور على الصفوف للحصول على العنصر الموافق للعمود الحالي. بعد ذلك طبّق منطق التحقق نفسه الذي استخدمته مع الصفوف، مع الانتباه إلى ' '.
ابدأ بتقليد
row_winnerثم عدّله ليعمل مع الأعمدة.لا يمكنك المرور على أعمدة
boardبسهولة كما تمر على صفوفها.ما العمود في قائمة متداخلة؟ العمود الأول هو أول عنصر من الصف الأول ثم أول عنصر من الصف الثاني، وهكذا.
المرور على كل الأعمدة يعني فحص أول عنصر من كل صف، ثم ثاني عنصر من كل صف، وهكذا.
إذًا تحتاج أولًا إلى المرور على أعداد تمثل المواضع: الأول والثاني وهكذا.
كيف تعرف عدد الأعمدة في
board؟بهذا تكتمل الحلقة الخارجية التي تمر على الأعمدة، ثم تحتاج إلى حلقة داخلية تمر على عناصر كل عمود.
عناصر العمود المختلفة ليست في الصف نفسه. كيف تصل إليها إذًا؟
يمكنك المرور على صفوف اللوحة والوصول إلى العنصر الذي يجمع الصف الحالي مع العمود الحالي.
مثلًا للوصول إلى كل عناصر العمود الخامس، مر على كل الصفوف وخذ العنصر الخامس من كل صف.
أنشئ متغيرًا منطقيًا لكل عمود، ثم حدّثه داخل الحلقة الداخلية.
بقية المنطق تشبه
row_winnerجدًا.انتبه إلى
' '.لا تنسَ إعادة
Falseفي النهاية عند الحاجة.
ممتاز! كان ذلك تحديًا جيدًا.
بقي التحقق من الأقطار الفائزة. في الفصل السابق كتبت دالة تعمل مع لوحة 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 على القطر الممتد من أسفل اليسار إلى أعلى اليمين.
عدد الأقطار الرئيسية يظل اثنين مهما كان حجم اللوحة. ابحث عن نمط الفهارس في القطر من أعلى اليسار إلى أسفل اليمين، ثم ابحث عن نمط القطر الآخر. يمكن أن تساعدك الفهرسة السالبة في القطر الثاني. بعد ذلك تحقق من أن جميع القيم على القطر متساوية وغير فارغة.
كم قطرًا توجد في لوحة مربعة مهما كان حجمها؟
حتى لو تغيّر حجم اللوحة، يظل عدد الأقطار نفسه.
هذه المرة لا يمكنك كتابة شيء مثل
middle == board[0][0] and middle == board[2][2]لأنك لا تعرف طول القطر.وقد لا يشترك القطران في عنصر واحد مثل
middleأصلًا.ابدأ بالقطر من أعلى اليسار إلى أسفل اليمين.
كيف تصل إلى عناصر هذا القطر باستخدام فهرسة مزدوجة؟
هل ترى نمطًا في الفهارس المزدوجة؟ جرّبه على الورق مع أمثلة.
انتقل الآن إلى القطر الآخر من أعلى اليمين إلى أسفل اليسار. يوجد نمط أيضًا لكنه أصعب قليلًا.
هل تتذكر الفهرسة السالبة؟ قد تساعدك هنا.
بعد فهم النمطين، استخدم الأفكار السابقة للتحقق أن كل العناصر متساوية.
يمكنك فحص القطرين في حلقة واحدة، أو استخدام حلقة مستقلة لكل قطر.
رائع! الآن نستطيع جمع الدوال الثلاث معًا.
اكتب دالة 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
)
الحل قصير جدًا إذا استخدمت الدوال الثلاث والعامل المنطقي المناسب.
الحل قصير جدًا؛ استخدم الدوال الثلاث بالشكل الصحيح.
فكر في الحالات الممكنة: متى تعيد
winner(board)القيمةFalse؟ ومتى تعيدTrue؟كيف تجمع نتائج الدوال الثلاث بعامل منطقي للحصول على النتيجة المطلوبة؟
عمل رائع!
أصبح لدينا الآن الكود الذي يحدد ما إذا كانت اللوحة تحتوي حالة فوز.