Python: нестандартные функции. Игра “Крестики-нолики”.

Продолжаем учить язык программирования пайтон Python. Переходим к изучению 6 главы “Функции. Игра Крестики-нолики” по книге: Майкл Доусон “Программируем на Python”, 2014 (Michael Dawson “Python Programming for the Absolute Beginner”, 3rd Edition), чтобы создавать собственные функции и работать с глобальными переменными.

Как создать функцию?

Общий вид функции в Python: название_функции()

Чтобы создать собственную функцию, нужно ее объявить.

Общий вид объявления функции

def название_функции(параметр1, параметр2...):
    '''Документирующая строка'''
    Блок выражений функции

Функции могут:

  • принимать значения через параметры,
  • возвращать значения через return.

Параметр функции

— это имя переменной в скобках после названия функции. В момент объявления функции параметр может не иметь конкретного значения. При вызове функции параметру нужно присвоить значение.

Позиционные параметры функции

В функции можно перечислить несколько параметров:

def название_функции(параметр1, параметр2...):

Вызов этой функции с позиционными аргументами:

название_функции('значение параметра1',значение параметра2...)

Вызов этой функции с именованными аргументами (для них не важен порядок!):

название_функции(параметр1='значение параметра1', параметр2=значение параметра2,...)

равнозначно…

название_функции(параметр2=значение параметра2, параметр1='значение параметра1',...)

Параметры по умолчанию

def название_функции(параметр1='значение', параметр2=значение...):

Если при вызове функции никакие другие значения параметрам не присвоятся, то отобразятся значения параметров по умолчанию.

Присваивая значение по умолчанию одному параметру, это нужно сделать для всех остальных параметров в объявляемой функции.

Параметры по умолчанию можно переопределить при вызове функции, при чем переопределять их можно и по отдельности, а не все сразу. Например, так:

Объявление функции ->

def название_функции(параметр1='значение', параметр2=значение):

Вызов функции ->

название_функции(параметр1='значение параметра1')

Функция, которая возвращает значения

def vozvrat():
    imya='kot'
    return imya

Программа-иллюстрация:

def vozvrat():
    imya='kot'
    return imya
print('Возврат: ', vozvrat())

Запуск программы дает:

Возврат:  kot
>>> 

Можно возвращать несколько значений через запись return p1,p2,p3...

Функция main()

Основной код программы можно не оставлять в глобальной зоне видимости, а поместить в функцию, которое можно дать любое имя, например, main()

Тогда код запуска программы будет выглядеть так:

main()
input('Нажмите Entr, чтобы выйти')

Глобальные переменные

Переменные внутри функции доступны только там. Есть глобальные переменные, которые объявляются вне функций, при этом они доступны внутри функций тоже, то только для “чтения”, если не указано обратное.

Глобальная переменная — создается в общей области видимости.

Локальная переменная — создается внутри функции.

Изменение глобальной переменной внутри функции

Чтобы менять глобальные переменные, находясь внутри функции, пишем global

Если создана глобальная переменная g_peremennaya, то изменить ее внутри функции можно написав: global g_peremennaya

Затенение глобальной переменной

Так как в общем случае внутри функции нельзя изменить значение глобальной переменной, но можно создать одноименную переменную внутри функции. Это и будет затенение. Новая одноименная переменная будет работать только внутри функции. Но это вносит путаницу, лучше так не делать.

Глобальные константы

Переменные мы изменяем по ходу кода. А константы — это переменные, записанные большими буквами (CONSTANTA), которые мы решили не менять. В общем случае лучше использовать глобальные константы, чем глобальные переменные.

Игра “Крестики-нолики”

Задача: написать программу, в которой пользователь может играть с компьютером в крестики-нолики на поле 3х3. Сначала показывается инструкция к игре. Далее идет выбор, кто будет ходить первым. Начинается игра, игроки делают ходы по очереди. Когда кто-то выигрывает или игра сыграна вничью, выводятся результаты и программа завершает работу.

Начнем с создания собственной функции, которая будет показывать пользователю инструкцию к игре:

def instrukciya():
    print('''
Привет! Это игра "Крестики-нолики".
Чтобы сделать ход, введи номер клетки,
куда хочешь поставить свой символ:

0 | 1 | 2
---------
3 | 4 | 5
---------
6 | 7 | 8


''')

Игра начинается с того, что пользователь выбирает, кто делает первый ход. Для этого напишем функцию, которая задает пользователю вопрос “Вы хотите быть первым, кто сделает ход (играть крестиками)?”, на который можно ответить “да” или “нет”. В объявлении функции можно на задавать конкретный вопрос, можно написать просто vopros, а потом при вызове функции в скобках написать любой текст.

Чтобы реализовать этот блок программы, сначала объявим первую общую функцию, которая будет задавать вопрос и получать ответ “да” или “нет”:

def nachalo(vopros):
    otvet=None
    while otvet not in ('да','нет'):
        otvet=input(question).lower()
    return otvet

Вторая часть функции присваивает игрокам тип фишек в зависимости от того, кто ходит первый, и будет состоять из уже объявленной функции nachalo(vopros):

def fishki():
    perviy_hod=nachalo("Вы хотите быть первым, кто сделает ход \
(играть крестиками)?  ")
    if perviy_hod=='да':
        print('Окей, ты играешь крестиками!')
        human=Х
        comp=O
    else:
        print('ОК, я делаю первый крестиками')
        human=O
        comp=Х
    return human, comp

В этих двух функциях мы используем имена переменных Х и O, чтобы компьютер правильно их использовал в игре, нужно дополнительно ввести эти переменные на глобальном уровне. То есть объяснить, что значит X и O:

Глобально задаем переменные:

X='X'
O='0'

Далее в программе нужно сделать функцию, которая будет спрашивать о том, куда пользователь хочет поставить свою фишку. Поле будет 3х3 и каждому полю условно дается свой номер от 0 до 8. То есть нужно сделать функцию, куда пользователь введет номер клетки для своего хода.

def hod(vopros,low,high):
    otvet=None
    while otvet not in range(low,high):
        otvet=int(input(vopros))
    return otvet

Сделаем функцию, которая генерирует игровые поля:

  • потребуется две глобальные константы: RAZMER_DOSKI и HODI
RAZMER_DOSKI=9 HODI=' '

Функция, создающая каждый раз новую доску с ходами выглядит так:

def new_doska():
    doska=[]
    for i in range(RAZMER-DOSKI):
        doska.append(HODI)
    return doska

Теперь нужно сделать функцию, которая будет показывать пользователю доску с результатом каждого хода:

def pokaz_doski(doska):
    print('\n', board[0], '|', board[1], '|', board [2])
    print('---------')
    print('\n', board[3], '|', board[4], '|', board [5])
    print('---------')
    print('\n', board[6], '|', board[7], '|', board [8], '\n')

Нам еще потребуется функция, которая проверяет, куда можно пойти. Для этого нужно сделать функцию с перебором всех пустых клеток, чтобы все незанятые места добавились в список, который и есть “доступные ходы”:

def dostupnie_hodi(doska):
    dostupnie_hodi=[]
    for i in range(RAZMER_DOSKI):
        if doska(i)== HODI:
            dostupnie_hodi.append(i)
    return dostupnie_hodi

Отдельно сделаем функцию, которая выводит победителя. Если победит игрок, ставящий крестики, то функция выдаст X, если победитель играл ноликами — 0, ничья задается глобальной константой NICHYA='Ничья', если игра еще не дошла до конца, то функция выдаст None. Есть способ определения победы через перебор всех возможных наборов из трех клеток, при которых игрок считается победителем:

def winner(doska):
    VAR_POBED=((0,1,2),
               (3,4,5),
               (6,7,8),
               (0,3,6),
               (1,4,7),
               (2,5,8),
               (0,4,8),
               (2,4,6))
    for i in varianti_pobed:
        if doska[i[0]]==doska[i[1]]==doska[i[2]]!=HODI:
            winner=doska[i[0]]
            return winner
        if HODI not in doska:
            return NICHYA
    return None

Следующая функция учитывает ходы пользователя, для этого нужно сначала понять, куда можно пойти, затем ожидается ввод пользователя числа из доступных. Функция принимает доску и тип фишек пользователя. В итоге работы функции пользователь делает ход:

def human_hod(doska,human):
    dostupnie=dostupnie_hodi(doska)
    hod=None
    while hod not in dostupnie:
        hod=hod('Твоя очередь ходить. Напиши номер поля 0-8: ',\
 0,RAZMER_DOSKI)
        if hod not in dostupnie:
            print('Поле занято. Напиши другой номер: ')
    print('Супер!')
    return hod

Пришло время самой сложной для меня функции — это ходы компьютера. Нужно, чтобы компьютер делал “хорошие” ходы, старался выиграть у пользователя. Ходить наугад можно, если компьютер будет выбирать любое свободное место на поле. Но тогда ему будет трудно выиграть.

Чтобы не “испортить” исходную доску, опытные программисты советуют создавать внутри функции “копию” доски, в которую мы и будем вносить изменения:

def comp_hod(doska,comp,human):
    doska=doska[:]

Чтобы компьютер не ходил “от балды”, учтем следующую стратегию:

  • “лучшее” поле – по центру;
  • если центральное поле уже занято, то “лучшими” считаются угловые поля;
  • компьютер ходит в “лучшее” из свободных мест на поле.

Лучшие поля задаются константой внутри функции:

BEST_HODI=(4,0,2,6,8,1,3,5,7)

В итоге вся функция ходов компьютера получается такой:

def comp_hod(doska,comp,human):
    doska=doska[:]
    BEST_HODI=(4,0,2,6,8,1,3,5,7)
    print('Мой ход: ')
    for i in dostupnie_hodi(doska):
        doska[i]=comp
        if winner(doska)==comp:
            print(hod)
            return hod
        doska[i]=HODI
    for j in dostupnie_hodi(doska):
        doska[j]=human
        if winner(doska)==human:
            print(hod)
            return hod
        doska[j]=HODI
    for k in dostupnie_hodi(doska):
        print(k)
        return k

Нужна еще одна функция, которая будет давать сделать ход следующему игроку, определяя какими фишками был сделан последний ход и передавая ход другим фишкам:

def next_hod(hod):
    if hod==X:
        return O
    else:
        return X

Потребуется еще функция, которая выявит победителя и поздравит пользователя с окончанием игры. Эта функция будет использоваться только в самом конце игры:

def pozdrav_pobeditela(pobeditel,comp,human):
    if pobeditel!=NICHYA:
        print('Собрана линия ', pobeditel)
    else:
        print(NICHYA)
    if pobeditel==comp:
        print('/n Компьютер выиграл!')
    elif pobeditel==human:
        print('Ты победил!')
    elif pobeditel==NICHYA:
        print(NICHYA)

Когда у нас есть все функции, нужно записать общую функцию main(), которая объединит все отдельные функции в единое целое:

def main():
    instrukciya()
    comp,human=fishki()
    hod=X
    doska=new_doska()
    pokaz_doski(doska)
    while not winner(doska):
        if i==human:
            hod=human_hod(doska,human)
            doska[hod]=human
        else:
            hod=comp_hod(doska,comp,human)
            doska[hod]=comp
        pokaz_doski(doska)
        i=next_hod(hod)
    pobeditel=winner(doska)
    pozdrav_pobeditela(pobeditel,comp,human)

Попробуем запустить программу с игрой “Крестики-Нолики”, для запуска потребуется всего две строки кода:

main()
input('\n Нажми Entr, чтобы выйти')

Запуск программы показал, что игра не работает как надо. Несмотря на то, что логика построения программы верная, в самих функциях где-то допущены ошибки. Нужно их исправить.

Исправление ошибок в игре “Крестики-нолики”.

Тестирование программы игры и исправление ошибок оказалось довольно трудной задачей, чтобы сделать это “на глаз”. Как искать ошибки в программе? Наверное, лучший способ — это протестировать работу каждой функции отдельно. Запустим каждую функцию и посмотрим, где есть проблемы:

Функция instrukciya() работает:

Привет! Это игра "Крестики-нолики".
Чтобы сделать ход, введи номер клетки,
куда хочешь поставить свой символ:

0 | 1 | 2
---------
3 | 4 | 5
---------
6 | 7 | 8



>>> 

Запускаем функцию fishki(), и с ней все в порядке:

Вы хотите быть первым, кто сделает ход (играть крестиками)?  да
Окей, ты играешь крестиками!
>>> 

В функции hod(low,high) я решила добавить конкретный вопрос, и ее запуск тоже прошел успешно со значениями 0-8 hod(0,8):

Делай свой ход - напиши номер поля (0-8): 2
>>> 

Запуск функции new_doska() ошибок не выдал, но и результата тоже, так как изначально этот блок пустой.

Спустя еще пару часов блужданий по коду, поиска ошибок, запусков отдельных функций и групп функций, я обнаружила, что в нескольких местах знак “крестика” X был написан разными символами, а также кое-где крестик стоял в кавычках. Еще некоторые мелочи были исправлены. Конечный вариант кода программы (которая заработала и выиграла у меня!) выглядит так:

X='X'
O='0'
RAZMER_DOSKI=9
HODI=' '
NICHYA='Ничья'

def instrukciya():
    print('''
Привет! Это игра "Крестики-нолики".
Чтобы сделать ход, введи номер клетки,
куда хочешь поставить свой символ:

0 | 1 | 2
---------
3 | 4 | 5
---------
6 | 7 | 8


''')
def nachalo(vopros):
    otvet=None
    while otvet not in ('да','нет'):
        otvet=input(vopros).lower()
    return otvet

def fishki():
    perviy_hod=nachalo("Вы хотите быть первым, кто сделает ход \
(играть крестиками)?  ")
    if perviy_hod=='да':
        print('Окей, ты играешь крестиками!')
        human=X
        comp=O
    else:
        print('ОК, я делаю первый ход крестиками')
        human=O
        comp=X
    return comp, human
        
def hod_number(low,high):
    otvet=None
    while otvet not in range(low,high):
        otvet=int(input("Делай свой ход - напиши номер поля (0-8): "))
    return otvet


def new_doska():
    doska=[]
    for i in range(RAZMER_DOSKI):
        doska.append(HODI)
    return doska

def pokaz_doski(doska):
    print('\n', doska[0], '|', doska[1], '|', doska [2])
    print('---------')
    print('\n'
          , doska[3], '|', doska[4], '|', doska [5])
    print('---------')
    print('\n', doska[6], '|', doska[7], '|', doska [8], '\n')

def dostupnie_hodi(doska):
    dostupnie_hodi=[]
    for i in range(RAZMER_DOSKI):
        if doska[i]== HODI:
            dostupnie_hodi.append(i)
    return dostupnie_hodi

def winner(doska):
    VAR_POBED=((0,1,2),
               (3,4,5),
               (6,7,8),
               (0,3,6),
               (1,4,7),
               (2,5,8),
               (0,4,8),
               (2,4,6))
    for i in VAR_POBED:
        if doska[i[0]]==doska[i[1]]==doska[i[2]]!=HODI:
            winner=doska[i[0]]
            return winner
        if HODI not in doska:
            return NICHYA
    return None
def human_hod(doska,human):
    dostupnie=dostupnie_hodi(doska)
    hod=None
    while hod not in dostupnie:
        hod=hod_number(0,RAZMER_DOSKI)
        if hod not in dostupnie:
            print('Поле занято. Напиши другой номер: ')
    print('Супер!')
    return hod
def comp_hod(doska,comp,human):
    doska=doska[:]
    BEST_HODI=(4,0,2,6,8,1,3,5,7)
    print('Мой ход: ')
    for i in dostupnie_hodi(doska):
        doska[i]=comp
        if winner(doska)==comp:
            print(i)
            return i
        doska[i]=HODI
    for j in dostupnie_hodi(doska):
        doska[j]=human
        if winner(doska)==human:
            print(j)
            return j
        doska[j]=HODI
    for k in dostupnie_hodi(doska):
        print(k)
        return k
def next_ochered(ochered):
    if ochered==X:
        return O
    else:
        return X
def pozdrav_pobeditela(pobeditel,comp,human):
    if pobeditel!=NICHYA:
        print('Собрана линия ', pobeditel)
    else:
        print(NICHYA)
    if pobeditel==comp:
        print('Компьютер выиграл!')
    elif pobeditel==human:
        print('Ты победил!')
    elif pobeditel==NICHYA:
        print(NICHYA)
def main():
    instrukciya()
    comp,human=fishki()
    ochered=X
    doska=new_doska()
    pokaz_doski(doska)
    while not winner(doska):
        if ochered==human:
            hod=human_hod(doska,human)
            doska[hod]=human
        else:
            hod=comp_hod(doska,comp,human)
            doska[hod]=comp
        pokaz_doski(doska)
        ochered=next_ochered(ochered)
    pobeditel=winner(doska)
    pozdrav_pobeditela(pobeditel,comp,human)

main()
input('\n Нажми Entr, чтобы выйти')

Мой первый матч в крестики-нолики с компьютером выглядел так:

Привет! Это игра "Крестики-нолики".
Чтобы сделать ход, введи номер клетки,
куда хочешь поставить свой символ:

0 | 1 | 2
---------
3 | 4 | 5
---------
6 | 7 | 8



Вы хотите быть первым, кто сделает ход (играть крестиками)?  да
Окей, ты играешь крестиками!

   |   |  
---------

   |   |  
---------

   |   |   

Делай свой ход - напиши номер поля (0-8): 4
Супер!

   |   |  
---------

   | X |  
---------

   |   |   

Мой ход: 
0

 0 |   |  
---------

   | X |  
---------

   |   |   

Делай свой ход - напиши номер поля (0-8): 5
Супер!

 0 |   |  
---------

   | X | X
---------

   |   |   

Мой ход: 
3

 0 |   |  
---------

 0 | X | X
---------

   |   |   

Делай свой ход - напиши номер поля (0-8): 2
Супер!

 0 |   | X
---------

 0 | X | X
---------

   |   |   

Мой ход: 
6

 0 |   | X
---------

 0 | X | X
---------

 0 |   |   

Собрана линия  0
Компьютер выиграл!

 Нажми Entr, чтобы выйти

 

This entry was posted in Python. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *