برمجة لعبة (X/O) بلغة البايثون




TicTacToe-AI-py-Fihmai
























Fihm ai

برمجة لعبة إكس أوُ بلغة البايثون

في هذا الدرس ستتعلم برمجة الآلة لاستخدام المنطق في لعبة إكس أوُ. سنقوم ببناء البيئة، و الإجراءات الممكن اتخاذها خلال اللعبة، و برمجة المنطق المتبع للفوز في اللعبة. في نهاية الدرس ستكون بنيت بيئة للعب إكس أوُ، و آلة تستعمل المنطق في اللعب ضدك وربما في الفوز عليك

بناء البيئة




بيئة اللعبة تتكون من المحيط الذي تتجسد فيه جميع التحركات والإجراءات التي يمكن للاعبين اتخاذها. وفي حالة لعبتنا فإن البيئة هي لوح ذو أربع خطوط متقاطعة تكوّن تسعة مساحات ممكن استحواذها من قبل اللاعبين


لبرمجة البيئة يمكننا تعريف دالة تستقبل المدخلات الحالية في اللعبة وتطبع حالة اللوح كما هو موضح في الدالة التالية


ملاحظة: يمكننا الاستفادة من هذه الدالة في طباعة البيئة في كل مرة يختار فيها أحد اللاعبين موقع الحركة التالية
In [ ]:
def drawBoard(board):

# تستقبل الدالة تحركات اللاعبين كقائمة وتقوم بطباعتها على شكل لوح لعبة إكس أوُ
# يتم تخزين التحركات في القائمة بدأً من الخانة 1 وليس 0 لسهولة استرجاع البيانات
# في كل خانة في القائمة المستلمة يمكن أن نجد قيمة إكس أو قيمة أوُ أو خانة خالية

    print('   |   |')
    print(' ' + board[7] + ' | ' + board[8] + ' | ' + board[9])
    print('   |   |')
    print('-----------')
    print('   |   |')
    print(' ' + board[4] + ' | ' + board[5] + ' | ' + board[6])
    print('   |   |')
    print('-----------')
    print('   |   |')
    print(' ' + board[1] + ' | ' + board[2] + ' | ' + board[3])
    print('   |   |')



برمجة أساسيات مسار اللعبة




الخطوة التالية هي فهم مسار اللعبة وبرمجة الخطوات التي تمر بها بدأً من تخيير اللاعب بين الحرفين إكس و أوُ


في الدالة التالية يقوم اللاعب بادخال الحرف لبدء اللعب
يتم استرجاع قائمة بالحرفين مع ملاحظة ترتيب حرف اللاعب أولاً متبعاً بحرف الآلة
In [ ]:
def inputPlayerLetter():

# X or O تمكّن اللاعب من اختيار الحرف المرغوب
# تقوم الدالة بتمرير قيمتين بحيث يتغير الترتيب بناءً على اختيار اللاعب. إذا اختار اللاعب حرف إكس يكون الأول في القائمة وهكذا
    letter = ''
    while not (letter == 'X' or letter == 'O'):
        print('Do you want to be X or O?')
        letter = input().upper()

    # القيمة الآولى حرف اللاعب والقيمة الثانية حرف الآلة
    if letter == 'X':
        return ['X', 'O']
    else:
        return ['O', 'X']



من يلعب أولاً؟



في لعبة إكس أوُ نلاحظ أن الأفضلية عادة لمن يلعب أولاً، فاحتمال فوز اللاعب ترتفع بشكل ملحوظ إذا كان الدور الأول من نصيبه. لضمان العدل في اللعبة نقوم ببرمجة دالة لاختيار من يبدأ أولاً بطريقة عشوائية كالتالي
In [ ]:
import random

def whoGoesFirst():

# Random يتم اختيار صاحب الدور الأول عشوائياً عن طريق تطويع الدال الرياضية 

    if random.randint(0, 1) == 0:
        return 'computer'
    else:
        return 'player'



الاستمرار في اللعب



كخاصية إضافية يمكن سؤال اللاعب عن رغبته في بدء لوحة جديدة بعد إنتهاء اللعبة. يمكننا كتابة الدالة كالتالي واستدعائها بعد تحديد الفائز في اللعبة السابقة
In [4]:
def playAgain():

# تقوم هذه الدالة بتمرير قيمة منطقية (صائب أو خطأ) بناءً على اختيار اللاعب في بدء جولة جديدة

    print('Do you want to play again? (yes or no)')
    return input().lower().startswith('y')



اتخاذ التحركات



للاحتفاظ بتحركات اللاعبين نقوم باستخدام قائمة تتكون من 10 خانات ونتجاهل القائمة ذات المؤشر 0 لتسهيل الرجوع للتحركات. بالتالي يتبقى 9 خانات يمكننا ربطها بموقع تحركات اللاعبين على اللوح وتسجيل الحرف في الخانة المناسبة


متى ما اختار اللاعب موقع الحركة التالية، نقوم باستدعاء الدالة التالية حيث يتم تعيين حرف اللاعب في موقع الحركة على قائمة اللعبة المذكورة سابقاّ في دالة بناء البيئة. حيث أن تلك القائمة هي ذاتها المستخدمة في رسم لوحة اللعب
In [5]:
def makeMove(board, letter, move):

    board[move] = letter



من الفائز؟



حالات الفوز في لعبة إكس أوُ محدودة جداً بسبب بساطة لوحة اللعب. إما أن تفوز بتكوين صف - ثلاثة احتمالات - أو تكوين عمود -ثلاثة احتمالات أخرى- أو بشكل قطري -احتمالين فقط- بالتالي يمكن الفوز بثمانية حالات لا تاسع لها
بتخصيص رقم لكل خانة على اللوح من 1 إلى 9 فإنه يمكننا التأكد من وجود فائز عن طريق التحقق من الآحرف الموجودة في كل ثلاثة خانات متجاورة. يمكن كتابة الشرط كما هو موضح في الدالة التالية حيث أن كل سطر يمثل حالة من حالات الفوز الثمانية. الحالة في السطر الأول تمثل وضح الحرف نفسه في الثلاثة الخانات العليا على اللوح... وهكذا
In [6]:
def isWinner(bo, le):

# يتم تمرير محتويات اللوح الحالية والحرف المنتمي للاعب إلى الدالة
# كل سطر يتحقق من أحد حالات الفوز الثمانية والشرط هو تحقيق أحد تلك الحالات لتمرير قيمة (صائب

    return ((bo[7] == le and bo[8] == le and bo[9] == le) or # across the top
            (bo[4] == le and bo[5] == le and bo[6] == le) or # across the middle
            (bo[1] == le and bo[2] == le and bo[3] == le) or # across the bottom
            (bo[7] == le and bo[4] == le and bo[1] == le) or # down the left side
            (bo[8] == le and bo[5] == le and bo[2] == le) or # down the middle
            (bo[9] == le and bo[6] == le and bo[3] == le) or # down the right side
            (bo[7] == le and bo[5] == le and bo[3] == le) or # diagonal
            (bo[9] == le and bo[5] == le and bo[1] == le)) # diagonal



التحقق من صلاحية الحركة




عند اختيار اللاعب -أو الآلة- لأي خانة على اللوح للعب فإننا بحاجة للتأكد من أن الخانة خاوية ويمكن تعيين الحرف فيها. بدلاً من برمجة خطوة التحقق في كود استراتيجية اللعب، نقوم بفصل التحقق كدالة مستقلة كالتالي
In [ ]:
def isSpaceFree(board, move):

# تقوم الدالة بتمرير القيمة المنطقية (صائب) في حال كانت الخانة المطلوبة شاغرة

    return board[move] == ' '



سؤال اللاعب عن حركته التالية على اللوح



نحتاج بناء دالة لسؤال اللاعب عن تحركاته والخانة التي يريد اللعب اختيارها. يمكننا ذلك عن طريق دالة المدخلات كما هو ظاهر في الدالة التالي. بالإضافة للحصول على رقم الخانة فإننا نتحقق من صلاحية المدخل عن طريق مقارنته بالمدخلات المقبولة -من 1 إلى 9- مع الأخذ في عين الاعتبار كون الخانة خالية. إن لم تكن القيمة المدخلة من قبل اللاعب ضمن الأرقام من 1 إلى 9 أو كانت الخانة قد تم اختيارها مسبقاً فإننا نسأل اللاعب مرة أخرى عن حركته التالية
In [9]:
def getPlayerMove(board):

    move = ' '
    # لطالما أن المستخدم لم يقم بادخال قيمة تستوفي الشروط، يستمر البرنامج في السؤال عن الحركة التالية
    # الشروط هي:
    # 1. يجب أن تكون القيمة المدخلة أحد قيم خانات اللوح من 1 إلى 9
    # 2. يجب أن تكون الخانة شاغرة
    while move not in '1 2 3 4 5 6 7 8 9'.split() or not isSpaceFree(board, int(move)):
        print('What is your next move? (1-9)')
        move = input()
    # في حال قام المستخدم بادخال قيمة صالحة تقوم الدالة بتمرير القيمة
    return int(move)



...ذكاء اصطناعي بدائي



نصل هنا للجزئية التي تهم الكثيرين وهي جزئية بناء الذكاء الاصطناعي وبرمجة منطق الآلة في لعب مباراة ضد ذكاء بشري. وجب التنويه هنا أنه وللتبسيط فإن المنطق المبرمج في الدالة التالية لا يتعلم من اخطائه السابقة ولا يتحسن مع مرور الوقت! الذكاء الاصطناعي المبني في هذا الدرس بدائي ويعتمد كلياً على المنطق واستراتيجيات اللعب وتعود بدائيته أولاً وأخيراً لسهول اللعبة التي نقوم ببرمجتها وإمكانية تطوير ذكاء اصطناعي بدائي يستطيع الفوز على اللاعب بدون تقنيات متقدمة وتعلم تعزيزي مستمر



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


  1. تتحقق الآلة من جميع الخانات وما إذا كان اختيار أي خانة يؤدي إلى الفوز المباشر
  2. في حال عدم وجود خانة تؤدي إلى فوز الآلة، تقوم الآلة بالبحث عن خانة تؤدي إلى فوز الخصم مباشرة
  3. أحد الاستراتيجيات الأساسية والمضمونة في لعب إكس أوُ هي اللعب في الآركان، وفي هذه الخطوة تقوم الآلة باختيار ركن عشوائي واللعب
  4. في حال كانت جميع الآركان محتلة فإن الآلة تحاول احتلال الخانة في المنتصف
  5. في حال لم تكن خانة المنتصف شاغرة فإن الآلة تختار عشوائياً أحد الخانات المتبقية في الأطراف


ملاحظة مهمة: الخطوات غير متسلسلة بمعنى أن الآلة لا تنتقل للخطوة التالية إلا إذا لم تتمكن من تحقيق متطلبات الخطوة السابقة


قمت بترقيم الكود في الدالة التالية لتوضيح الكود المعني بكل خطوة من خطوات المنطق المذكورة في الأعلى
In [11]:
def getComputerMove(board, computerLetter):

    # بناءً على حرف الآلة الذي تستلمه الدالة فإنها تقوم بتعريف متغير لحرف اللاعب كالتالي
    if computerLetter == 'X':
        playerLetter = 'O'
    else:
        playerLetter = 'X'
        
    # Tic Tac Toe AI خوارزمية الذكاء الاصطناعي :
    # أولاً تتحق الآلة من إمكانية الفوز في حركة واحدة وتحتل الخانة اللازمة للفوز
    #----------------------(1)-----------------------------
    for i in range(1, 10):
        copy = getBoardCopy(board)
        if isSpaceFree(copy, i):
            makeMove(copy, computerLetter, i)
            if isWinner(copy, computerLetter):
                return i
    #----------------------(1)-----------------------------

    # تتحقق الآلة من إمكانية اللاعب من الفوز في حركة واحدة ومنعه عن طريق احتلال الخانة
    #----------------------(2)-----------------------------
    for i in range(1, 10):
        copy = getBoardCopy(board)
        if isSpaceFree(copy, i):
            makeMove(copy, playerLetter, i)
            if isWinner(copy, playerLetter):
                return i
    #----------------------(2)-----------------------------


    # تحاول الآلة احتلال أحد الأركان في حال كان شاغر
    #----------------------(3)-----------------------------
    move = chooseRandomMoveFromList(board, [1, 3, 7, 9])
    if move != None:
        return move
    #----------------------(3)-----------------------------


    # تحاول الآلة احتلات خانة المنتصف في حال لازالت شاغرة
    #----------------------(4)-----------------------------
    if isSpaceFree(board, 5):
        return 5
    #----------------------(4)-----------------------------


    # في محاولة أخيرة تقوم الآلة باختيار خانة عشوائية من الخانات المتبقية
    #----------------------(5)-----------------------------
    return chooseRandomMoveFromList(board, [2, 4, 6, 8])
    #----------------------(5)-----------------------------



اختيار خانة عشوائية من قائمة تم تمريرها



نلاحظ في الدالة السابقة استخدام دالة خاصة في الخطوتين الرابعة والخامسة لاختيار خانة عشوائية مع توفير قائمة لاختيار أحد قيمها بطريقة عشوائية. في الدالة التالية نقوم بتطبيق هذا المفهوم عن طريق التحقق أولاً من صلاحية اللعب في خانات تلك القائمة ثم اختيار احد الخانات عشوائياً وإعادتها للدالة السابقة. في حال كانت جميع التحركات في خانات القائمة المرسلة غير ممكنة فإن الدالة تعيد القيمة الخالية
In [10]:
def chooseRandomMoveFromList(board, movesList):

    possibleMoves = []
    for i in movesList:
        if isSpaceFree(board, i):
            possibleMoves.append(i)
            
    if len(possibleMoves) != 0:
        return random.choice(possibleMoves)
    else:
        return None



دوال إضافية مساعدة



الدالتين التالية دوال مساعدة في تنظيم الكود واستخراج العمليات المتكررة في دالة مستقلة


الدالة الآولى تستلم قائمة الخانات على اللوح وتقوم بتمرير نسخة من تلك القائمة. والدالة الثانية تتحقق من أن اللوح مازال صالح للعب وتستخدم للتحقق من انتهاء اللعبة أو إتاحة لعب دور إضافي
In [7]:
def getBoardCopy(board):

    dupeBoard = []
    for i in board:
        dupeBoard.append(i)
    return dupeBoard


In [12]:
def isBoardFull(board):

    for i in range(1, 10):
        if isSpaceFree(board, i):
            return False
    return True



مسار اللعب



بعد بناء البيئة وتحديد أساسيات اللعبة وتوضيح منطق اللعبة وإجراءاتها للآلة يأتي وقت بدء اللعب، البشر ضد الآلة


في هذه المرحلة يصعب شرح الكود التالي بدون الإشارة للأسطر لذلك سأقوم بشرح الكود عن طريق التعليق خلال الأسطر
In [ ]:
# Play XO
#الترحيب باللاعب قبل بدأ الجولة
print('Welcome to XO!')

#while loopتبدأ الجولة هنا وتنتهي متى ما اختار اللاعب إنهاء الجولات عن طريق الخروج المباشر من ال
while True:
    # نقوم في بداية كل جولة بمسح التحركات من على اللوح وتعيين قيمة مسافة خالية لكل خانة
    # نعين عشر خانات للقائمة ونتجاهل الخانة ذات المؤشر 0 بحيث تمثل كل خانة من التسع الباقية خانات اللوح
    
    #    7 | 8 | 9
    #   ---+---+---
    #    4 | 5 | 6
    #   ---+---+---
    #    1 | 2 | 3
    
    theBoard = [' '] * 10
    
    # عند استدعاء دالة ادخال اللاعب للحرف المرغوب فإن الدالة تمرر قيمتين: حرف اللاعب وحرف الآلة
    playerLetter, computerLetter = inputPlayerLetter()
    # يتم اختيار صاحب الدور الأول عشوائياً
    turn = whoGoesFirst()
    print('The ' + turn + ' will go first.')
    gameIsPlaying = True
    
    # لطالما أن الجولة قائمة ولم تنتهي بالتعادل أو فوز أحد اللاعبين
    while gameIsPlaying:
        if turn == 'player':
        # في حال بدء دور اللاعب
            # يتم رسم اللوح
            drawBoard(theBoard)
            # يُسأل اللاعب عن الخانة المراد احتلالها
            move = getPlayerMove(theBoard)
            # يتم تخزين حركة اللاعب على اللوح
            makeMove(theBoard, playerLetter, move)
            
            # نتحقق مما إذا فاز اللاعب
            if isWinner(theBoard, playerLetter):
                # يتم رسم اللوح وإعلام اللاعب بفوزه وإنهاء الجولة الحالية
                drawBoard(theBoard)
                print('Hooray! You have won the game!')
                gameIsPlaying = False
            else:
                # إذا تأكدنا من عدم فوز اللاعب يأتي دور التحقق من التعادل في حال كان اكتمال اللوح
                if isBoardFull(theBoard):
                    drawBoard(theBoard)
                    print('The game is a tie!')
                    break
                else:
                    # ما لم يفز اللاعب ولم يتحقق التعادل بعد، ينتقل الدور إلى الآلة
                    turn = 'computer'
        
        else:
        # في حال بدء دور الآلة
            # نستدعي منطق الآلة مع تمرير اللوح الحالي ونستلم الحركة التي اختارتها الآلة بناءً على المنطق المذكور سابقاً
            move = getComputerMove(theBoard, computerLetter)
            # نقوم بتخزين حركة الآلة على اللوح
            makeMove(theBoard, computerLetter, move)
            
            # نتحقق مما إذا فاز اللاعب  
            if isWinner(theBoard, computerLetter):
                # يتم رسم اللوح وإعلام اللاعب بخسارته وإنهاء الجولة الحالية
                drawBoard(theBoard)
                print('The computer has beaten you! You lose.')
                gameIsPlaying = False
            else:
                # إذا تأكدنا من عدم فوز الآلة يأتي دور التحقق من التعادل في حال اكتمال اللوح
                if isBoardFull(theBoard):
                    drawBoard(theBoard)
                    print('The game is a tie!')
                    break
                else:
                    # ما لم يخسر اللاعب ولم يتحقق التعادل، ينتقل الدور إلى اللاعب
                    turn = 'player'
                    
    # في حالة إنتهاء الجولة يتم سؤال اللاعب عما إذا كان يريد بدء جولة جديدة
    # في حالة الرفض يتم إنهاء اللعبة
    if not playAgain():
        break







Welcome to Tic Tac Toe!
Do you want to be X or O?
X
The computer will go first.
   |   |
   |   | O
   |   |
-----------
   |   |
   |   |  
   |   |
-----------
   |   |
   |   |  
   |   |
What is your next move? (1-9)
1
   |   |
   |   | O
   |   |
-----------
   |   |
   |   |  
   |   |
-----------
   |   |
 X |   | O
   |   |
What is your next move? (1-9)
6
   |   |
 O |   | O
   |   |
-----------
   |   |
   |   | X
   |   |
-----------
   |   |
 X |   | O
   |   |
What is your next move? (1-9)
8
   |   |
 O | X | O
   |   |
-----------
   |   |
   | O | X
   |   |
-----------
   |   |
 X |   | O
   |   |
The computer has beaten you! You lose.
Do you want to play again? (yes or no)


In [ ]:
 









تعليقات

Popular Posts

أبرز ست وظائف سيقضي عليها الذكاء الاصطناعي

أربعة أمور بديهية للبشر تصعب على الذكاء الإصطناعي

سبعة مبادئ أساسية للذكاء الإصطناعي من شركة قوقل

أربعة مراحل لتطبيقات الذكاء الإصطناعي

أربعة استخدامات للذكاء الإصطناعي في التعليم

خمسة طرق لزيادة أرباح الشركات بالذكاء الإصطناعي

أبرز أربعة تقنيات عملية للذكاء الإصطناعي

أربع مجالات ذات قيمة اقتصادية عالية للذكاء الإصطناعي

خمس سلبيات مباشرة للذكاء الإصطناعي