cb
codebamin
الفصل 11 · مشروع لعبة إكس-أو (Tic-Tac-Toe)

فحص صفوف اللوحة لتحديد الفائز (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 تحتوي على صف فائز: خط أفقي تكون جميع خاناته الرمز نفسه، بشرط ألا يكون هذا الرمز هو المسافة ' ':

__copyable__ 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 في الصف الأخير.

أحسنت!

اكتب الآن دالة مشابهة اسمها column_winner تتحقق من وجود عمود فائز، أي خط رأسي يحتوي على الرمز نفسه في جميع خاناته:

__copyable__ 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 في العمود الثاني.

لا يمكنك المرور على الأعمدة مباشرة بالطريقة نفسها التي تمر بها على الصفوف. العمود الأول مثلًا يتكوّن من العنصر الأول في كل صف، والعمود الثاني من العنصر الثاني في كل صف، وهكذا. لذلك ستحتاج إلى المرور على أرقام مواضع الأعمدة، ثم المرور على الصفوف للحصول على العنصر الموافق للعمود الحالي. بعد ذلك طبّق منطق التحقق نفسه الذي استخدمته مع الصفوف، مع الانتباه إلى ' '.

ممتاز! كان ذلك تحديًا جيدًا.

بقي التحقق من الأقطار الفائزة. في الفصل السابق كتبت دالة تعمل مع لوحة 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 وهكذا:

__copyable__ 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 على القطر الممتد من أسفل اليسار إلى أعلى اليمين.

عدد الأقطار الرئيسية يظل اثنين مهما كان حجم اللوحة. ابحث عن نمط الفهارس في القطر من أعلى اليسار إلى أسفل اليمين، ثم ابحث عن نمط القطر الآخر. يمكن أن تساعدك الفهرسة السالبة في القطر الثاني. بعد ذلك تحقق من أن جميع القيم على القطر متساوية وغير فارغة.

رائع! الآن نستطيع جمع الدوال الثلاث معًا.

اكتب دالة winner تستقبل board وتعيد True إذا كانت اللوحة تحتوي على صف فائز أو عمود فائز أو قطر فائز، وتعيد False في غير ذلك.

يجب أن يعتمد حل winner على استدعاء الدوال الثلاث. لا تجعل winner نفسها تنفذ حلقات أو فهرسة أو تعيد تنفيذ المنطق من الصفر.

إليك تنفيذات جاهزة لـrow_winner وcolumn_winner وdiagonal_winner مع اختبارات لـwinner. انسخ الكود وأكمل الفراغ فقط:

__copyable__ 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 )

الحل قصير جدًا إذا استخدمت الدوال الثلاث والعامل المنطقي المناسب.

عمل رائع!

أصبح لدينا الآن الكود الذي يحدد ما إذا كانت اللوحة تحتوي حالة فوز.

solution.py
1
2
3
4
5
6
7
8
9
10
الطرفية (shell)
>>>