Python: Задачи и решения (Глава 6. Функции. Игра “Крестики-нолики”).

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

Задача: Доработайте функцию ask_number() или hod_number() так, чтобы ее можно было вызывать еще с одним параметром — кратностью (величиной шага). Сделайте шаг по умолчанию равным 1.

Первоначальный вид функции:

def hod_number(low,high):
    otvet=None
    while otvet not in range(low,high):
        otvet=int(input("Делай свой ход - напиши номер поля (0-8): "))
    return otvet

Доработанный вариант функции:

def hod_number(low,high,step=1):
    otvet=None
    while otvet not in range(low,high,step):
        otvet=int(input("Делай свой ход - напиши номер поля (0-8): "))
    return otvet

Задача: Доработайте игру «Отгадай число» из главы 3 так, чтобы в ней нашла применение функция ask_number() она же hod_number().

Исходный код игры “Отгадай число”:

import random 
print('Я загадала целое число от 0 до 100. Угадай с 5 попыток!') 
chislo=random.randrange(101) 
popitka_count=0 
while popitka_count<=4:
    popitka_count+=1
    popitka=int(input('Это число:')) 
    if popitka<chislo: 
        print('Больше!') 
    elif popitka>chislo: 
        print('Меньше!') 
    else:
        print('Ничего себе! Ты отгадал! Это правда',chislo) 
        print('Количество попыток:',popitka_count) 
        break
if popitka_count==5 and popitka!=chislo:
    print('О, ужас! Ты совершенно не умеешь читать мои мысли!\n\
Так и не смог удагать число за 5 попыток :(')

Доработанная программа с функцией hod_number():

import random 

print('Я загадала целое число от 0 до 100. Угадай с 5 попыток!') 
chislo=random.randrange(101)
popitka=None
def hod_number(low=0,high=5):
    popitka_count=0
    while popitka_count in range(low,high):
        popitka=int(input("Это число: "))
        popitka_count+=1
        if popitka<chislo: 
            print('Больше!') 
        elif popitka>chislo: 
            print('Меньше!') 
        elif popitka==chislo:
            print('Ничего себе! Ты отгадал! Это правда',chislo)
            print('Количество попыток:',popitka_count)
            break
    return popitka
hod_number()
if popitka!=chislo:
    print('О, ужас! Ты совершенно не умеешь читать мои мысли!\n\
Так и не смог угадать число за 5 попыток :(')

Запуск доработанной игры “Отгадай число” выглядит так:

Я загадала целое число от 0 до 100. Угадай с 5 попыток!
Это число: 56
Больше!
Это число: 68
Больше!
Это число: 76
Меньше!
Это число: 70
Больше!
Это число: 74
Меньше!
О, ужас! Ты совершенно не умеешь читать мои мысли!
Так и не смог угадать число за 5 попыток :(
>>> 

Еще один вариант доработанной функции:

def hod_number(LOW=0,HIGH=5):
    popitka=int(input("Это число: "))
    return popitka

Задача: Доработайте новую версию игры «Отгадай число» (которую вы создали, решая предыдущую задачу) так, чтобы основная часть программы стала функцией main(). Для того чтобы игра началась, не забудьте вызвать эту функцию глобально.

Решение:

def hod_number(LOW=0,HIGH=5):
    popitka=int(input("Это число: "))
    return popitka
def main():
    LOW=0
    HIGH=5
    popitka=0
    popitka_count=0
    print('Я загадала целое число от 0 до 100. Угадай с 5 попыток!')
    chislo=random.randrange(101)
    while popitka_count in range(LOW,HIGH):
        hod_number()
        popitka_count+=1
        if popitka<chislo: 
            print('Больше!') 
        elif popitka>chislo: 
            print('Меньше!') 
        elif popitka==chislo:
            print('Ничего себе! Ты отгадал! Это правда',chislo)
            print('Количество попыток:',popitka_count)
            break
    if popitka!=chislo:
        print('О, ужас! Ты совершенно не умеешь читать мои мысли!\n\
Так и не смог угадать число за 5 попыток :(')
import random
main ()
input('Нажмите Entr, чтобы выйти.')

Запуск программы игры:

Я загадала целое число от 0 до 100. Угадай с 5 попыток!
Это число: 50
Больше!
Это число: 60
Больше!
Это число: 70
Больше!
Это число: 80
Больше!
Это число: 90
Больше!
О, ужас! Ты совершенно не умеешь читать мои мысли!
Так и не смог угадать число за 5 попыток :(
Нажмите Entr, чтобы выйти.
>>>

Задача: Напишите такую функцию computer_move() или comp_hod(), которая сделала бы стратегию компьютера безупречной. Проверьте, можно ли создать непобедимого противника.

Исходный код игры “Крестики-нолики”:

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, чтобы выйти')

Исходная функция comp_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

Беспроигрышная стратегия компьютера в крестики-нолики возможна в 4/5 случаев, если его ход компьютера первый. Как выиграть в крестики-нолики если ходишь вторым — вопрос более интересный, ведь в моей программе пользователь сам выбирает, ходить первым или нет. Если зайти в википедию на игру “Крестики-нолики”, то можно найти все возможные стратегии выигрыша в этой игре и возможности сыграть в ничью. 

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

  1. В первую очередь нужно проверить, можно ли выиграть следующим ходом, и так пойти.
  2. Если выиграть не получится, то нужно проверить, не может ли выиграть следующим ходом противник, и пойти туда.

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

  • если компьютер ходит первым (за крестики), то нужно пойти в центр, а затем ходить в такой свободный угол, который располагается дальше всего от последнего хода ноликов, а если так пойти нельзя — то ход выбирается рандомно;
  • если компьютер ходит вторым (играет за нолики), то проверяем куда был сделан первый ход:
    • если крестики пошли в центр, то ходить в любой угол, а если все углы уже заняты, то в любое место;
    • если крестики пошли первым ходом в угол, то сначала занимаем центр, а затем ходим в угол напротив первого хода крестиков, если таких вариантов нет — ходим по сторонам;
    • если крестики сделали первый ход по сторонам поля, ходим в центр,
      • если дальше крестики пошли в угол — ходим в угол на противоположной стороне (крест-на-крест),
      • если дальше крестики пошли в стороны напротив, ходим в любые углы,
      • если дальше крестики пошли по стороне рядом со своим первым ходом — ходим в угол рядом с крестиками.

Теперь осталось записать эту логику в нашу программу. Используем константу BEST_HODI, куда запишем лучшие ходы по приоритетам для компьютера:

def comp_hod(doska,comp,human):
    doska=doska[:]
    BEST_HODI=(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
    if comp==X and 4 in dostupnie_hodi(doska):
        print (4)
        return 4
        doska[4]=HODI
    if comp==X and 4 not in dostupnie_hodi(doska):
        for k in BEST_HODI:
            if BEST_HODI[k] in dostupnie_hodi(doska):
                print(k)
                return k

Стратегия компьютера сильно улучшилась по сравнению с первоначальным вариантом. Запуск игры выглядит теперь так:

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

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



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

   |   |  
---------

   |   |  
---------

   |   |   

Мой ход: 
4

   |   |  
---------

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

   |   |   

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

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

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

   |   |   

Мой ход: 
2

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

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

   |   |   

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

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

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

   |   | 0 

Мой ход: 
6

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

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

 X |   | 0 

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

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

Как сделать искусственный интеллект нашего компьютерного игрока еще более умным?

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

Следующее важное условие для создания непобедимого компьютера — важно, чтобы компьютер предпочитал центр углам.

Таким образом, опишем алгоритм непобедимого компьютера:

  • проверка на наличие хода для выигрыша компьютера;
  • проверка на наличие выигрышного следующего хода противника, чтобы его заблокировать;
  • проверка на возможность создания “вилки”, но если есть возможность заблокировать будущие “вилки” противника — сделать это предпочтительно;
  • проверить центр;
  • проверить углы;
  • проверить боковые стороны;
  • в случае, когда можно выбрать любой ход, делать это рандомно.
  •  
This entry was posted in Python. Bookmark the permalink.

Leave a Reply

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