Вчера мы рассказали, как теория игр работает на практике и помогает победить. Вот кратко основное:
- теория игр — это не только про игры, а про любые ситуации, в которых может что-то происходить, меняться и зависеть от разных факторов;
- игрок — это тот, кто находится в этой ситуации и принимает решения;
- смысл теории игр в том, чтобы понять, как нужно действовать игрокам в разных ситуациях, чтобы получить нужный результат. Но не интуитивно, а с точки зрения законов математики.
В прошлой статье мы разобрали игру Ним с точки зрения логики и математики. Сегодня мы реализуем её на Python.
Классические правила игры Ним звучат так:
Есть несколько кучек, в каждой из которых лежит сколько-то камней. За один ход игрок может взять из любой одной кучки любое ненулевое число камней. Игроки берут камни по очереди. Побеждает тот, кто забирает последние камни.
Что делаем
Пишем игру Ним с двумя кучками — количество камней в кучках будет больше одного, за раз можно брать сколько угодно камней.
Играть будем против компьютера — он случайным образом будет выбирать кучку и количество камней, которые будет забирать. Кто заберёт последние камни, тот и победил.
Для этого нам понадобится Python — с ним получится быстро написать код и сразу проверить его в деле.
Логика проекта
Сегодня сделаем простую версию игры, которая будет работать так:
- есть две кучки камней, случайным образом выбираем, где сколько лежит;
- игрок и компьютер ходят по очереди;
- текущий участник выбирает номер кучки и количество камней, которые нужно оттуда забрать;
- алгоритм проверяет, остались ли после этого камни в кучках: если не осталось — игра заканчивается;
- ход переходит к другому участнику, и всё повторяется заново.
Компьютер будет выбирать кучку и количество камней не по сложной логике, а просто случайным выбором. Мы не будем пока добавлять логику проверки выигрышности хода, а сосредоточимся на механике игры, а проверку прикрутим в следующий раз.
Подготавливаем переменные
Для игры нам нужен модуль случайных чисел и переменные для кучек. Ещё добавим название текущего игрока — это поможет при проверках в дальнейшем. Начинать будет игрок, поэтому сразу пропишем его в переменной:
# подключаем модуль для работы со случайными числами
import random
# формируем кучки с камнями
stack_1 = random.randint(2,10)
stack_2 = random.randint(2,10)
# выбранная кучка
select = 0
# сколько камней взяли из кучки
taken = 0
# кто делает текущий ход
current_player = 'Игрок'
Забираем камни из кучки
Чтобы взять камни из кучки, нам нужно знать две вещи: из какой кучки забирать и сколько камней брать. Сделаем отдельную функцию, в которой на вход подаётся кучка и её номер; а внутри будет выбор, что делать для каждого игрока.
Если ходит человек, то он сам выбирает количество камней, которые нужно забрать, и мы это значение возьмём из переменной taken. А если ходит компьютер, мы случайным образом выберем для него кучку, в которой есть камни, и количество камней для отбора.
После этого нам остаётся вывести состояние текущего хода и уменьшить кучку на выбранное число камней. Результат работы функции — новое количество камней в выбранной кучке:
# забираем камни из кучки
def take(stack, num):
# будем менять глобальную переменную внутри функции
global taken
# если ход делает компьютер
if current_player == 'Компьютер':
# случайным образом выбираем количество камней, которые заберём из кучки
taken = random.randint(1,stack)
# выводим состояние текущего хода
print(current_player + ' взял ' + str(taken) + ' камней из ' + str(num) + ' кучки')
# уменьшаем кучку на выбранное количество камней
stack = stack - taken
# возвращаем количество камней в кучке
return(stack)
Выводим текущее количество камней
На старте игры и после каждого хода нам важно знать, сколько камней лежит в каждой кучке. Для этого напишем простую функцию — она сообщит нам о состоянии кучек и заодно проверит, не выиграл ли кто-то после своего хода. Если выиграл — выводится сообщение и игра останавливается.
# выводим текущее состояние кучек
def result():
# сообщаем, сколько камней в каждой кучке
print('Камней в 1 кучке: ' + str(stack_1))
print('Камней в 2 кучке: ' + str(stack_2))
# если в кучках не осталось камней — текущий игрок победил
if stack_1 == 0 and stack_2 == 0:
print('Игра окончена, победил ' + current_player)
# выходим из программы
exit()
Программируем ход компьютера
Для начала компьютеру нужно проверить, в каких кучках есть камни: если в одной кучке нет камней, значит, берём из другой. Если камни есть в обеих кучках — случайным способом выбираем номер кучки и количество камней, которые надо забрать. Так как мы не проверяем выигрышность, ходы компьютера могут быть совсем не оптимальными:
# ход компьютера
def take_computer():
# будем работать с глобальными переменными
global stack_1, stack_2, current_player
# если в первой кучке нет камней
if stack_1 == 0:
# берём камни из второй
stack_2 = take(stack_2, 2)
# то же самое для второй кучки
elif stack_2 == 0:
stack_1 = take(stack_1, 1)
# если камни в кучках есть
else:
# выбираем случайным образом номер кучки
choice = random.randint(1,2)
# если выбрана первая кучка — берём камни оттуда
if choice == 1:
stack_1 = take(stack_1, 1)
# то же самое для второй кучки
else:
stack_2 = take(stack_2, 2)
Запускаем игру
Последнее, что нам осталось сделать, — это запустить саму игру. Для этого на старте выводим начальное состояние кучек и запускаем бесконечный цикл. Бесконечный — для того, чтобы он работал всё время, пока в кучках есть камни. Как только они закончатся, функция result()
выведет сообщение о победе и остановит игру.
Ещё на каждом ходу мы меняем в конце текущего игрока: если был компьютер, ставим игрока, и наоборот. С игроком действуем чуть иначе, чем с компьютером, — нам нужно узнать у игрока номер кучки и количество камней, а потом отправить эти данные в функцию отбора камней.
# выводим на старте состояние кучек
result()
# запускаем бесконечный цикл
while True:
# если ходит компьютер
if current_player == 'Компьютер':
# он делает свой ход
take_computer()
# выводим состояние кучек
result()
# меняем текущего игрока
current_player = 'Игрок'
# если ходит игрок
else:
# спрашиваем у него номер кучки и количество камней
select = int(input('Выберите кучку: '))
taken = int(input('Сколько камней забрать: '))
# если игрок выбрал первую кучку — берём оттуда
if select == 1:
stack_1 = take(stack_1, 1)
# а если вторую — то оттуда
else:
stack_2 = take(stack_2, 2)
# выводим состояние кучек
result()
# меняем текущего игрока
current_player = 'Компьютер'
# подключаем модуль для работы со случайными числами
import random
# формируем кучки с камнями
stack_1 = random.randint(2,10)
stack_2 = random.randint(2,10)
# выбранная кучка
select = 0
# сколько камней взяли из кучки
taken = 0
# кто делает текущий ход
current_player = 'Игрок'
# забираем камни из кучки
def take(stack, num):
# будем менять глобальную переменную внутри функции
global taken
# если ход делает компьютер
if current_player == 'Компьютер':
# случайным образом выбираем количество камней, которые заберём из кучки
taken = random.randint(1,stack)
# выводим состояние текущего хода
print(current_player + ' взял ' + str(taken) + ' камней из ' + str(num) + ' кучки')
# уменьшаем кучку на выбранное количество камней
stack = stack - taken
# возвращаем количество камней в кучке
return(stack)
# выводим текущее состояние кучек
def result():
# сообщаем, сколько камней в каждой кучке
print('Камней в 1 кучке: ' + str(stack_1))
print('Камней в 2 кучке: ' + str(stack_2))
# если в кучках не осталось камней — текущий игрок победил
if stack_1 == 0 and stack_2 == 0:
print('Игра окончена, победил ' + current_player)
# выходим из программы
exit()
# ход компьютера
def take_computer():
# будем работать с глобальными переменными
global stack_1, stack_2, current_player
# если в первой кучке нет камней
if stack_1 == 0:
# берём камни из второй
stack_2 = take(stack_2, 2)
# то же самое для второй кучки
elif stack_2 == 0:
stack_1 = take(stack_1, 1)
# если камни в кучках есть
else:
# выбираем случайным образом номер кучки
choice = random.randint(1,2)
# если выбрана первая кучка — берём камни оттуда
if choice == 1:
stack_1 = take(stack_1, 1)
# то же самое для второй кучки
else:
stack_2 = take(stack_2, 2)
# выводим на старте состояние кучек
result()
# запускаем бесконечный цикл
while True:
# если ходит компьютер
if current_player == 'Компьютер':
# он делает свой ход
take_computer()
# выводим состояние кучек
result()
# меняем текущего игрока
current_player = 'Игрок'
# если ходит игрок
else:
# спрашиваем у него номер кучки и количество камней
select = int(input('Выберите кучку: '))
taken = int(input('Сколько камней забрать: '))
# если игрок выбрал первую кучку — берём оттуда
if select == 1:
stack_1 = take(stack_1, 1)
# а если вторую — то оттуда
else:
stack_2 = take(stack_2, 2)
# выводим состояние кучек
result()
# меняем текущего игрока
current_player = 'Компьютер'
Что дальше
Кажется, что программа получилась большой, но на самом деле в ней хватает недочётов:
- Нет проверки на номер кучки: игрок может ввести число 3, и программа остановится с ошибкой.
- То же самое — с количеством камней, которые берёт игрок. При этом у компьютера с этим всё в порядке, он не может взять больше, чем там есть.
- Нет проверки на выигрышность хода — и это самое главное.
Исправим всё это в следующий раз и сразу добавим режим работы с двумя игроками. Подпишитесь, чтобы не пропустить продолжение проекта.