От простого к сложному, пишем на Python логическую игру  «Быки и Коровы». В этой игре игрок должен угадать четырёхзначное число за меньшее число ходов. Игрок вводит число, а компьютер даёт подсказки.

Программирование - это игра, в которой вы можете создавать свои собственные правила и где конечным результатом будет что угодно, что вы из этого сможете сделать.

Линус Торвальдс

Аннотация

Методическая разработка “Программирование логических игр на Python”, как учебное пособие, предназначена для занятий с детьми старше 10 лет в кружке программирование. Синтаксису и семантике языка программирования Python здесь уделяется не достаточное для изучения языка Python внимание. Мы считаем, что данное пособие будет полезно детям и взрослым как введение в специальность. Программисты ещё не знакомые с языком Python с этим пособием смогут оценить выразительность языка Python и некоторые возможности графической библиотеки Tkinter. Дети могут самостоятельно запустить на компьютере в среде Python 3  примеры из нашего пособия. Примеры сопровождаются очень подробными комментариями и описанием работы программ. Заинтересованным в более глубоком изучении языка Python могу порекомендовать мобильное приложение Sololearn.

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

Введение

Python [ˈpʌɪθ(ə)n], в русском языке питон — высокоуровневый язык программирования общего назначения, ориентированный на повышение производительности разработчика и читаемости кода. Синтаксис ядра Python минималистичен. В то же время стандартная библиотека включает большой набор полезных функций.

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

Разработка языка Python была начата в конце 1980-х годов сотрудником голландского института CWI Гвидо ван Россумом.

На сегодня поддерживается одна ветка развития Python 3.x, поддержка ветки Python 2.x закончилась в апреле 2020 года.

Wikipedia

Для программирования на Python на компьютере должен быть установлен интерпретатор Python. В операционной системе Linux он установлен по умолчанию. В Windows и на компьютер Apple установить Python не сложно. Скачать дистрибутив Python можно с официального сайта Python.

Писать программы на Python следует в простых текстовых редакторах, которые не вносят в код программы свои теги форматирования. Например, в Windows это Notepad. Мы рекомендуем установить на компьютер и использовать специализированную среду разработки программ (IDE) на Python, например, IDLE или Thonny. Эти IDE бесплатны и доступны для установки в любой операционной системе. Устанавливая IDE IDLE под Windows, выбирайте дистрибутив для Python 3 последней версии. в процессе установки IDLE будет установлен и интерпретатор Python.

Сохраняйте программы на Python с расширением py. Например, имя файла может быть таким: BullsAndCows02.py. Где py - расширение имени файла, BullsAndCows - имя файла, 02 - версия программы.

Запускать программы на Python можно из командной строки. При этом интерпретатор командной строки должен быть открыт в папке с вашей программой. Наберите в командной строке python3 BullsAndCows02.py и нажмите Ввод (Enter). Командная строка в Windows - это cmd, в Linux и на Apple это терминал. В Windows, программы на python можно запускать двойным щелчком. И самое простое, запускайте программы на python из среды разработки программ (IDE) IDLE или Thonny.

Правила игры «Быки и Коровы»

По правилам игры, компьютер случайным образом выбирает четырёхзначное число таким образом, чтобы первой значащей цифрой не был 0 и чтобы все цифры, составляющие это число были бы уникальными. Если в предложенном вами числе несколько цифр оказались на своём месте, компьютер сообщает сколько есть таких цифр. Цифра на своём месте называется Быком. Если в предложенном вами числе несколько цифр оказались не на своём месте, компьютер сообщает сколько есть таких цифр. Цифра не на своём месте называется Коровой.

В западных источниках подобная игра-головоломка упоминается как "Master-mind". Но, "Master-mind" и в подмётки не годится нашим Быкам и Коровам. В школьные годы мы играли в игру "Быки и Коровы" на листочках в клеточку.

Функция choice() из библиотеки random, строки

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

from  random import choice              # Импортируем функцию choice из библиотеки random
z = '0123456789'                        # Создаём строку z
x = choice(z)                           # Создаём строку x из одного случайно выбранного символа из z
print(x)

Лист. 1. Программа, случайным образом выбирающая 1 букву из строки.

Нам показалось, что функция choice() из библиотеки random сможет справиться с этой задачей. В нашей программе см. листинг 1:

  1. С помощью ключевых слов from и import мы импортировали в нашу программу функцию choice() из библиотеки random.
  2. Создали строку из всех арабских цифр и присвоили этой строке имя z. Теперь в нашей программе существует переменная z. 
  3. Создали строку x (переменную x) из одного случайно выбранного с помощью функции choice() символа из строки z.
  4. С помощью функции print() вывели в командную строку интерпретатора Python значение переменной x.

Строки в Python создаются с помощью одинарных или двойных кавычек.

Переменные имеют имя и им поставлены в соответствие значения. 

Запускаем программу листинг 1 несколько раз и получаем несколько, случайным образом выбранных цифр. См. скриншот 1.

===== RESTART: BullsAndCows01.py =====
2
>>> 
===== RESTART: BullsAndCows01.py =====
0
>>>
===== RESTART: BullsAndCows01.py =====
3
>>>

Скр. 1. Вывод программы листинг 1.

Программа, случайным образом выбирающая 4 буквы из списка

Напомним, по правилам игры, компьютер случайным образом выбирает четырёхзначное число. Если в программе листинг 1 после строчки x = choice(z) 3 раза запустить

 x = x + choice(z)

то мы получим строку, состоящую из 4-х случайным образом выбранных цифр. Мы не можем вместо строчки x = choice(z) 4 раза запустить x = x + choice(z). Строчка программы x = choice(z) нужна, так как мы должны создать переменную до того как первый раз будем использовать её значение. А в строке программы x = x + choice(z) переменной x мы присваиваем значение переменной x + случайным образом выбранный из строки z символ. В строке программы x = x + choice(z) мы складываем 2 строки - старое значение строки x и строку (символ) возвращённую функцией choice(z).

Программисты предпочитают использовать короткую запись оператора сложения с присваиванием, если переменная участвует в арифметическом выражении слева и справа от знака присваивания (=):

x = x + choice() заменяют на x += choice() 

from  random import choice              # Импортируем функцию choice из библиотеки random
z = '0123456789'                        # Создаём строку z
x = choice(z)                           # Создаём строку x из одного случайно выбранного символа из z
for i in range(3):                      # В цикле из 3-x повторений
    x += choice(z)                      # добавляем к строке x случайно выбранные символы из строки z
print(x)

Лист. 2. Программа, случайным образом выбирающая 4 буквы из списка.

В программе см. листинг 2, после строки x = choice(z) мы добавили строку x += choice(z). Запись x += choice(z) это короткий эквивалент x = x + choice(z).

В Python имеется несколько сокращённых форм записи выражений +=, -=, *=, %=, /=.

Строчка программы x += choice(z) находится в теле цикла for. В программе листинг 2 тело цикла for выполняется 3 раза. Цикл for создан следующим образом:

  • После ключевого слова for указывают имя переменной, которая последовательно будет принимать значения из списка или диапазона указанного после ключевого слова in. Когда элементы списка или диапазона закончатся, цикл будет завершён.
  • В программе см. листинг 2, числовой диапазон для цикла for мы получили с помощью функции range(). Функция range(3) создаёт числовой диапазон из трёх чисел {0, 1, 2}.
  • В конце строки определяющей параметры цикла for ставят :
  • Тело цикла в Python выделяется отступами от начала строки. Рекомендуется добавлять 4 пробела.

В программе (см. листинг 2), числовой диапазон для цикла for мы можем заменить списком [0, 1, 2] или даже строкой так как значение переменной цикла i в этой программе в теле цикла не используется. Поэкспериментируйте с таким циклом:

for i in 'Жук':

===== RESTART: BullsAndCows02.py =====
0293
>>> 
===== RESTART: BullsAndCows02.py =====
6331
>>> 
===== RESTART: BullsAndCows02.py =====
5374
>>> 

Скр. 2. Вывод программы листинг 2.

Мы запустили программу (листинг 2) 3 раза и сразу же обнаружили несколько недоработок. Смотрите скриншот 2.

  1. Случайные числа иногда начинаются с 0.
  2. Цифры в случайных числах иногда повторяются.

Перечисленные недостатки противоречат правилам игры. Займёмся решением обнаруженных проблем.

Улучшенная программа, выбирающая число из диапазона от 1023 до 9876 

Сформулируем задачу на модернизацию программы (листинг 2):

  1. Устранить возможность появления 0 на первом знакоместе случайного числа.
  2. Устранить возможность повторного выбора случайных цифр.

Решение указанных задач можно видеть на листинге 3:

from  random import choice                  # Импортируем функцию choice из библиотеки random
z = '0123456789'                            # Создаём строку z
x = choice(z[1:10])                         # Создаём строку x из одного случайно выбранного символа из среза строки z (без 0)
for i in range(3):                          # В цикле из 3-x повторений
    z = ''.join(j for j in z if j != x[i])  # удаляем из строки z символ который добавили в строку x,
    x += choice(z)                          # добавляем к строке x случайно выбранные символы из строки z.
    print(z)
print(x)

Лист. 3. Программа, случайным образом создающая строку, представляющую число от 1023 до 9876 такое, что цифры, составляющие это число, не повторяются.

Программа (листинг 3) увеличилась всего на одну строку. Для решения выше поставленной задачи мы:

  1. изменили аргумент функции choice() в 3-й строке программы с z на z[1:10],
  2. добавили строковый метод join() в теле цикла for программы.

Чем отличается строка z от строки z[1:10]?

Запустите в командной строке интерпретатора Python следующее:

>>> z = '0123456789'
>>> z
'0123456789'
>>> z[1:10]
'123456789'
>>>

В случае z[1:10] от строки z отсечён первый символ. Напомним, индексация символов в строке и элементов в других структурах Python начинается с 0. В случае z[1:10] мы получили срез строки z с элементами строки z (буквами) с индексами {1, 2, 3, 4, 5, 6, 7, 8, 9}. Теперь, функция choice(z[1:10]) случайным образом выбирает один символ из строки '123456789'.

Что делает строковый метод join()? Функция join(list) объединяет строки, списки и другие перечислимые объекты и возвращает объединённую строку. В строках можно перечислить буквы по порядку от 0-го символа до последнего. Посмотрим на нашем примере (листинг 3) что получает функция join() в качестве параметра если предположить что x = '26'. Заметьте, что всё это выражение j for j in z if j != x[i] есть параметр функции join() в программе листинг 3.

>>> z = '0123456789'
>>> x = '26'
>>> for j in z:
	    if j not in x:
		    print(j)

		
0
1
3
4
5
7
8
9
>>>

Выражение j for j in z if j != x[i] в программе (листинг 3) передаёт функции join() список символов строки z исключая символы, которые уже входят в строку x.

Выражение j for j in z if j != x[i] называется генератор объекта или генераторное выражение <generator object <genexpr>>. Посмотрим на нашем примере (листинг 3) что получает функция join() в качестве параметра если предположить что x = '26'. Убираем цикл для лучшей читаемости кода:

>>> z = '0123456789'
>>> x = '26'
>>> print([j for j in z if j != x[0] and j != x[1]])
['0', '1', '3', '4', '5', '7', '8', '9']
>>>

В строке z = ''.join(j for j in z if j != x[i]) функция join() объединяет список символов строки z исключая символы, которые уже входят в строку x. В программе (листинг 3) в цикле for с тремя повторениями усечённую строку z выводит на экран функция print(z). Мы это видим на скриншоте 3:

===== RESTART: BullsAndCows03.py =====
012345678
01234568
0124568
9738
>>> 
===== RESTART: BullsAndCows03.py =====
013456789
01345689
0345689
2715
>>>

Скр. 3. Вывод программы листинг 3.

По поводу функции join() остаётся добавить, что она может объединять в строку перечислимые объекты вставляя между ними строку разделитель, указанную перед функцией join() через точку. В программе (листинг 3) там указана пустая строка '' (две одинарные кавычки). Поэкспериментируйте с разделителем.

Подытожим, программа (листинг 3) создаёт строку из четырёх случайно выбранных арабских цифр, причём первой значащей цифрой не может быть 0 и все цифры, составляющие это число уникальны. Это соответствует правилам игры.

Метод split()

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

Александр @DaneSoul

В Python есть функция split(), точнее будет сказать строковый метод split(). Функция split() применяется к объектам класса строка, поэтому она является методом применимым к строкам.

Метод split() делит строку на на фрагменты по разделителю, указанному в качестве первого параметра. Второй, не обязательный параметр указывает сколько раз будет разбита строка. Метод split() возвращает объект список подстрок исходной строки, разделитель при этом отбрасывается.

В нашей программе (листинг 3) мы можем заменить сложное для понимания выражение j for j in z if j != x[i] методом split():

from  random import choice                  # Импортируем функцию choice из библиотеки random
z = '0123456789'                            # Создаём строку z
x = choice(z[1:10])                         # Создаём строку x из одного случайно выбранного символа из среза строки z (без 0)
for i in range(3):                          # В цикле из 3-x повторений
    z = ''.join(z.split(x[i]))              # удаляем из строки z символ который добавили в строку x,
    x += choice(z)                          # добавляем к строке x случайно выбранные символы из строки z.
    print(z)
print(x)

Лист. 3а. Программа, случайным образом создающая строку, представляющую число от 1023 до 9876 такое, что цифры, составляющие это число, не повторяются.

===== RESTART: BullsAndCows03a.py =====
012356789
01235679
0123567
4891
>>> 
===== RESTART: BullsAndCows03a.py =====
012345689
01234568
0123458
7968
>>> 
===== RESTART: BullsAndCows03a.py =====
012456789
01456789
0156789
3245
>>>

Скр. 3а. Вывод программы листинг 3.

Программы листинг 3 и листинг 3а работают одинаково.

Функция input()

from  random import choice                  # Импортируем функцию choice из библиотеки random
z = '0123456789'                            # Создаём строку z
x = choice(z[1:10])                         # Создаём строку x из одного случайно выбранного символа из среза строки z (без 0)
for i in range(3):                          # В цикле из 3-x повторений
    z = ''.join(z.split(x[i]))              # удаляем из строки z символ который добавили в строку x,
    x += choice(z)                          # добавляем к строке x случайно выбранные символы из строки z.
y = input("Введите четырёхзначное число: ")
print ('Ваше число:', y, 'Моё число:', x)

Лист. 4. Программа принимает строку от игрока и выдаёт случайным образом сформированное четырёхзначное число.

В программе (листинг 4) мы добавили строку с функцией input(). Функция input() выводит в командную строку интерпретатора Python сообщение и ожидает ввод строки от пользователя. Пользователь вводит текст и завершает ввод нажатием на клавишу «Ввод» (На некоторых клавиатурах «Enter» или «Return»). Функция input() возвращает в программу введённую пользователем строку.

Так же, в программе (листинг 4) мы усовершенствовали вывод в функции print(). Теперь, с программой  (листинг 4) можно играть в игру «Угадай ка». Смотрите скриншот 4.

===== RESTART: BullsAndCows04.py =====
Введите четырёхзначное число: 1234
Ваше число: 1234 Моё число: 4652
>>>

Скр. 4. Вывод программы листинг 4.

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

Сравнение числа введённого игроком с числом загаданным компьютером

Цифры которые совпадают по знакоместу в числе введённом пользователем и в загаданном числе назовём Быки. Быков будем подсчитывать в переменной b. Цифры которые не совпадают по знакоместу в числе введённом пользователем но присутствуют в загаданном числе на другом месте назовём Коровы. Коров будем подсчитывать в переменной c.

from  random import choice                  # Импортируем функцию choice из библиотеки random
z = '0123456789'                            # Создаём строку z
x = choice(z[1:10])                         # Создаём строку x из одного случайно выбранного символа из среза строки z (без 0)
for i in range(3):                          # В цикле из 3-x повторений
    z = ''.join(z.split(x[i]))              # удаляем из строки z символ который добавили в строку x,
    x += choice(z)                          # добавляем к строке x случайно выбранные символы из строки z.
print('Загадано число:', x)
y = input("Введите четырёхзначное число: ") # Функция ввода строки
b = 0; c = 0                                # Создаём переменные Bulls и Cows
for i in range(4):                          # В цикле из 4-x повторений
    if x[i] == y[i]:                        # проверяем цифра на своём месте,
        b += 1                              # если да, то добавляем быка,
    elif y[i] in x:                         # если нет, проверяем есть ли в загаданном числе эта цифра?
        c += 1                              # если да, то добавляем корову
print(y + ' содержит ' + str(b) + ' быка и ' + str(c) + ' коровы')

Лист. 5.Программа принимает строку от игрока, сравнивает её со случайным образом сформированным четырёхзначным числом и выдаёт результат.

В программе (листинг 5) мы сравниваем введённые пользователем цифры с загаданным случайным числом в цикле for из четырёх повторений. Переменная цикла i будет использоваться как индекс строки x и строки y. Условный оператор if сравнивает i-тый элемент списка x с i-тым элементом списка y.

Логический оператор == оператор сравнения, он сравнивает значение справа от знака == и значение слева. Если значения справа и слева равны, то результатом логической операции сравнения будет True (истина), иначе False (ложь).

Если x[i] == y[i], значит найден бык и переменная b в строке b += 1 будет увеличена на 1. Если x[i] не равно y[i] выполняется условный оператор elif. В операторе elif в нашей программе (листинг 5) с помощью оператора in проверяется присутствует ли элемент y[i] в строке x. Логический оператор in проверяет входит ли строка слева от оператора in как подстрока в строку записанную справа от оператора in. y[i] in x возвращает значение истина (отвечает ДА) если i-тый элемент (цифра) из строки y имеется в строке x. Если элемент y[i] найден в x, значит найдена корова и выполняется строка c += 1, которая увеличивает переменную c на 1.

Далее цикл for выполняется сначала и так продолжается пока все 4 цифры введённые игроком не будут проверены.

Необходимо заметить, что строку b += 1 можно записать как b = b + 1. Интерпретатор Python складывает старое значение переменной b с 1 и присваивает полученный результат переменной b.

Можно описать по порядку и более точно, что происходит в это время в мозгу Python:

  1. b = 0 Python создаёт объект со значением 0, а в b записывает адрес этого объекта (делает ссылку на объект).
  2. b += 1 или b = b + 1 Python, по адресу записанному в b берёт значение 0, прибавляет к 0 единицу (0+1), создаёт новый объект с полученным результатом (1), а в b записывает адрес этого объекта.
  3. Теперь на объект со значением 0 нет ссылок и объект со значением 0 уничтожается.

Для проверки правильности работы программы (листинг 5) мы добавили в программу строчку print('Загадано число:', x), чтобы знать, какое число загадано компьютером. Так мы можем оценить, правильно ли компьютер считает «Быков» и «Коров». Смотрите скриншот 5.

===== RESTART: BullsAndCows05.py =====
Загадано число: 9754
Введите четырёхзначное число: 9876
9876 содержит 1 быка и 1 коровы
>>>

Скр. 5. Вывод программы листинг 5.

Цикл while

Программа (листинг 5) позволяет нам сделать только один ход в игре «Быки и Коровы», а мы хотим играть до победы. Значит, программа должна позволить вводить нам числа для проверки компьютером столько раз, сколько понадобится для нахождения искомого числа.

from  random import choice                      # Импортируем функцию choice из библиотеки random
z = '0123456789'                                # Создаём строку z
x = choice(z[1:10])                             # Создаём строку x из одного случайно выбранного символа из среза строки z (без 0)
for i in range(3):                              # В цикле из 3-x повторений
    z = ''.join(z.split(x[i]))                  # удаляем из строки z символ который добавили в строку x,
    x += choice(z)                              # добавляем к строке x случайно выбранные символы из строки z.
while True:
    y = input("Введите четырёхзначное число: ") # Функция ввода строки
    b = 0; c = 0                                # Создаём переменные Bulls и Cows
    for i in range(4):                          # В цикле из 4-x повторений
        if x[i] == y[i]:                        # проверяем цифра на своём месте,
            b += 1                              # если да, то добавляем быка,
        elif y[i] in x:                         # если нет, проверяем есть ли в загаданном числе эта цифра?
            c += 1                              # если да, то добавляем корову
    print(y + ' содержит ' + str(b) + ' быка и ' + str(c) + ' коровы')

Лист. 6. Программа игры «Быки и Коровы», без контроля окончания игры.

Программа (листинг 6) отличается от программы (листинг 5) тем, что мы добавили в неё бесконечный цикл while. Цикл while выполняется до тех пор, пока истинно условие выполнения цикла. Мы записали в качестве условия выполнения цикла логическую константу True. True переводится как истина. Таким образом, записав while True: мы создали бесконечный цикл. Телом этого цикла являются все строки программы, расположенные под ключевым словом while с отступом от левого края окна с программой.

===== RESTART: BullsAndCows06.py =====
Введите четырёхзначное число: 1234
1234 содержит 0 быка и 3 коровы
Введите четырёхзначное число: 5678
5678 содержит 0 быка и 1 коровы
Введите четырёхзначное число: 7890
7890 содержит 0 быка и 0 коровы
Введите четырёхзначное число: 6890
6890 содержит 0 быка и 0 коровы
Введите четырёхзначное число: 2345
2345 содержит 2 быка и 1 коровы
Введите четырёхзначное число: 2351
2351 содержит 1 быка и 2 коровы
Введите четырёхзначное число: 2415
2415 содержит 4 быка и 0 коровы
Введите четырёхзначное число:

Скр. 6. Вывод программы листинг 6.

Отметим, что цикл while прерывается если, условие выполнения цикла принимает значение False (ложь) или в теле цикла встретится оператор break.

Выход из цикла

Программа (листинг 6) позволяет нам вводить числа для проверки компьютером бесконечное раз, но по правилам игры игрок должен угадать четырёхзначное число за меньшее число ходов. Значит, наша программа должна считать количество ходов и информировать об окончании игры. Бесконечный цикл while True должен прерваться тогда, когда игрок найдёт искомое число.

from  random import choice                      # Импортируем функцию choice из библиотеки random
z = '0123456789'                                # Создаём строку z
x = choice(z[1:10])                             # Создаём строку x из одного случайно выбранного символа из среза строки z (без 0)
for i in range(3):                              # В цикле из 3-x повторений
    z = ''.join(z.split(x[i]))                  # удаляем из строки z символ который добавили в строку x,
    x += choice(z)                              # добавляем к строке x случайно выбранные символы из строки z.
n = 0                                           # Счётчик ходов
while True:
    y = input("Введите четырёхзначное число: ") # Функция ввода строки
    n += 1                                      # Увеличиваем счётчик ходов на 1
    b = 0; c = 0                                # Создаём переменные Bulls и Cows
    for i in range(4):                          # В цикле из 4-x повторений
        if x[i] == y[i]:                        # проверяем цифра на своём месте,
            b += 1                              # если да, то добавляем быка,
        elif y[i] in x:                         # если нет, проверяем есть ли в загаданном числе эта цифра?
            c += 1                              # если да, то добавляем корову
    print(y + ' содержит ' + str(b) + ' быка и ' + str(c) + ' коровы')
    if b == 4:                                  # Если число угадано
        print('Вы победили за', n, 'ходов')     # Победа
        break                                   # Выход из игры

Лист. 7. Программа игры «Быки и Коровы», с вводом/выводом в командную строку интерпретатора Python.

В программе (листинг 7) мы добавили переменную n - счётчик ходов и добавили проверку на совпадение загаданного числа с числом введённым игроком.

Проверку совпадения загаданного числа с числом введённым игроком в программе (листинг 7) делает условный оператор if в строке

if b == 4:

Если найдено 4 быка, то в командную строку интерпретатора Python выводится сообщение "Вы победили за n ходов", где переменная n заменяется на подсчитанное число ходов. А так же, если найдено 4 быка, выполняется оператор break, прерывается бесконечный цикл while True и на этом программа завершается.

===== RESTART: BullsAndCows07.py =====
Введите четырёхзначное число: 1234
1234 содержит 1 быка и 0 коровы
Введите четырёхзначное число: 5678
5678 содержит 1 быка и 0 коровы
Введите четырёхзначное число: 1290
1290 содержит 2 быка и 1 коровы
Введите четырёхзначное число: 1098
1098 содержит 1 быка и 2 коровы
Введите четырёхзначное число: 1970
1970 содержит 4 быка и 0 коровы
Вы победили за 5 ходов
>>>

Скр. 7. Вывод программы листинг 8.

Программа (листинг 7) работоспособна и функционально полностью соответствует правилам игры, смотрите скриншот 7. Но, опять но!

Графический интерфейс к программе.

Писать такие программы как программа листинг 7, может быть, учат в школе. Но такими программами (играми) с интерфейсом командной строки уже более 35 лет никто не пользуется. Есть класс задач, где программы с интерфейсом командной строки широко используются и ещё долго будут востребованы - это утилиты. Мы же написали игру, а компьютерные игры уже давно обзавелись графическим интерфейсом. Игра «Быки и Коровы», надеюсь, станет более привлекательной если мы её снабдим графическим интерфейсом.

Сначала, создадим графический интерфейс (GUI) для программы «Быки и Коровы». GUI для программы «Быки и Коровы» будем создавать с помощью графической библиотеки Tkinter.

Библиотека Tkinter предназначена для организации диалогов в программах написанных на языке на Python с помощью оконного графического интерфейса GUI. Tkinter основана на более универсальной библиотеке Tk. Обычно, она входит в состав дистрибутива Python. Tkinter позиционируется как библиотека для быстрого написания GUI-приложений.

From, import, Tk(), geometry(), title()

В начале программы (см. листинг 10) с помощью ключевых слов from и import импортируем все функции из библиотеки Tkinter в нашу программу. На то что мы импортируем все функции указывает звёздочка в строке

from tkinter import *

Библиотека tkinter входит в состав Python 3 и старше. Библиотека Tkinter входит в состав Python 2.7 и младше. Обе библиотеки будут совместимы на уровне тех функций, которые мы будем использовать в нашей программе и, следовательно, наша программа будет работать с Python 3 и с Python 2.7. Надо только заменить имя библиотеки tkinter на имя Tkinter в первой строчке программы, и ещё кое какие мелочи. Впрочем, рекомендуется использовать последние версии Python 3, а Python 2.7 уже уходит в историю.

from tkinter import *                           # Импортируем все функции из библиотеки TKinter
from  random import choice                      # Импортируем функцию choice из библиотеки random

tk = Tk()                                       # Создаём окно пиложения
tk.geometry('500x350')                          # Задаём размер окна пиложения в пикселях
tk.title('Быки и Коровы')                       # Название программы в заголовке окна

Лист. 10. Создание окна для игры «Быки и Коровы».

В программе (Листинг 10):

  1. метод Tk() из библиотеки tkinter создаёт главное окно программы,
  2. объекту главное окно программы мы присваиваем имя tk,
  3. объекту tk (главному окну программы) методом geometry() мы задаём размер в пикселях,
  4. для объекта tk методом title() мы меняем заголовок окна.

Рис. 1. Окно для игры «Быки и Коровы».

Message(), pack(), config()

У каждой игры есть правила игры. В следующей версии программы мы, с помощью конструктора объекта Message() библиотеки tkinter, добавим в главное окно программы окно сообщений, в которое выведем правила игры.

from tkinter import *                           # Импортируем все функции из библиотеки TKinter
from  random import choice                      # Импортируем функцию choice из библиотеки random

tk = Tk()                                       # Создаём окно пиложения
tk.geometry('500x350')                          # Задаём размер окна пиложения в пикселях
tk.title('Быки и Коровы')                       # Название программы в заголовке окна

msg = Message(width=400, padx=10, pady=5,
              text='Введите следующее число от 1023 до 9876 '
              'такое, чтобы цифры, составляющие это число,'
              ' не повторялись!')               # Создаём окно сообщений
msg.pack(padx=10, pady=5)                       # Размещаем окно сообщений в окне tk
msg.config(bg='light grey', fg='olive',
           font=('times', 12, 'italic'))        # Параметры для msg

Лист. 11. Создание окна для игры «Быки и Коровы».

В программе (Листинг 11):

  1. метод Message(), конструктор из библиотеки tkinter создаёт объект окно сообщений в главном окне программы,
  2. объекту класса Message ( окно сообщений) мы присваиваем имя msg,
  3. параметры метода Message():
    1. width - ширина в пикселях,
    2. bg - цвет фона,
    3. padx - набивка справа и слева в пикселях (отступы внутри от края окошка),
    4. pady - набивка сверху и снизу в пикселях (отступы внутри от края окошка),
    5. text - выводимый на экран текст,
  4. объект msg мы размещаем в главном окне программы методом pack(),
  5. параметры метода pack():
    1. padx - набивка справа и слева в пикселях (отступы снаружи от края окошка),
    2. pady - набивка сверху и снизу в пикселях (отступы снаружи от края окошка),
    3. expand - разрешает/запрещает выделять объекту всё доступное пространство
    4. anchor - привязать объект к (E, NE, N, NW, W, SW, S, SE и CENTER) сторонам света.

Рис. 2. Окно для игры «Быки и Коровы».

Entry(), focus()

Для игры «Быки и Коровы» нам понадобится поле ввода символов с клавиатуры. Поле ввода добавим в главное окно программы с помощью конструктора объекта Entry() библиотеки tkinter.

from tkinter import *                           # Импортируем все функции из библиотеки TKinter
from  random import choice                      # Импортируем функцию choice из библиотеки random

tk = Tk()                                       # Создаём окно пиложения
tk.geometry('500x350')                          # Задаём размер окна пиложения в пикселях
tk.title('Быки и Коровы')                       # Название программы в заголовке окна

msg = Message(width=400, padx=10, pady=5,
              text='Введите следующее число от 1023 до 9876 '
              'такое, чтобы цифры, составляющие это число,'
              ' не повторялись!')               # Создаём окно сообщений
msg.pack(padx=10, pady=5)                       # Размещаем окно сообщений в окне tk
msg.config(bg='light grey', fg='olive',
           font=('times', 12, 'italic'))        # Параметры для msg

ent = Entry(width=4)                            # Создаём поле ввода
ent.pack()                                      # Размещаем поле ввода в окне tk
ent.focus()                                     # Поместить фокус в поле ввода

Лист. 12. Создание GUI интерфейса для игры «Быки и Коровы».

В программе (Листинг 12):

  1. метод Entry() конструктор из библиотеки tkinter создаёт объект поле ввода (окошко) в главном окне программы,
  2. параметр width=4 в методе Entry() определяет ширину поля ввода в 4 символа,
  3. объекту класса Entry (поле ввода) мы присваиваем имя ent,
  4. объект ent мы размещаем в главном окне программы методом pack().
  5. методом focus() передаём в объект ent фокус ввода, помещаем в него курсор, то есть, делаем это поле активным.

Рис. 3. Окно для игры «Быки и Коровы».

Наша игра «Быки и Коровы» рассчитана на игру человека (игрока) с компьютером. Игрок вводит число, компьютер отвечает, есть ли в этом числе цифры совпадающие с цифрами в загаданном числе по знакоместу (быки) и сколько угадано цифр которые находятся не на своём месте (коровы). Для диалога игрока с программой, кроме поля ввода, нам понадобится вывод информации из программы. Создадим в нашей программе ещё одно поле класса Message() с именем msg2 и компьютер будет в это поле выводить количество быков и коров. Но, в начале игры, пусть в поле msg2 выводится уточняющая информация о быках и коровах.

from tkinter import *                           # Импортируем все функции из библиотеки TKinter
from  random import choice                      # Импортируем функцию choice из библиотеки random

tk = Tk()                                       # Создаём окно пиложения
tk.geometry('500x350')                          # Задаём размер окна пиложения в пикселях
tk.title('Быки и Коровы')                       # Название программы в заголовке окна

msg = Message(width=400, padx=10, pady=5,
              text='Введите следующее число от 1023 до 9876 '
              'такое, чтобы цифры, составляющие это число,'
              ' не повторялись!')               # Создаём окно сообщений
msg.pack(padx=10, pady=5)                       # Размещаем окно сообщений в окне tk
msg.config(bg='light grey', fg='olive',
           font=('times', 12, 'italic'))        # Параметры для msg

ent = Entry(width=4)                            # Создаём поле ввода
ent.pack()                                      # Размещаем поле ввода в окне tk
ent.focus()                                     # Поместить фокус в поле ввода

msg2 = Message(width=400, padx=10, pady=5,
              text='Бык - цифра на своём месте.\n'
               'Корова - цифра не на своём месте.')     # Создаём окно сообщений
msg2.pack(padx=10, pady=5)                      # Размещаем окно сообщений в окне tk
msg2.config(font=('times', 12, 'normal'))       # Параметры для msg

Лист. 14. Создание GUI интерфейса для игры «Быки и Коровы».

В программе листинг 14 текст в поле msg2 мы разбили на две строку с помощью экранированного n (\n). Обратная наклонная черта \ в строках экранирует следующий за ней символ. Экранированный символ не печатается, а превращается в управляющий символ. В частности, управляющая последовательность символов \n переводит вывод следующего текста на новую строку.

Рис. 4. Окно для игры «Быки и Коровы».

Def, bind(), mainloop(), get()

Программа, листинг 14 линейная, выполняется построчно от первой сроки до последней. Но, нам нужна программа, способная вести бесконечный диалог с игроком. Для создания бесконечного цикла в конце линейной программы поместим функцию mainloop(). Функция mainloop() цикл ожидания событий в программах с библиотекой tkinter.

В самом конце программы листинг 15 будет запущена функция mainloop(). Mainloop() будет выполняться бесконечно. В функции mainloop() будут отслеживаться все события, происходящие с окном вашей программы и будут выполняться методы (функции) вызываемые этими событиями. Например, даже если вы ещё не зарегистрировали в Вашей программе ни одного обработчика события, в вашей программе всё равно отслеживаются такие события, как клик кнопкой мыши по кнопкам свернуть, развернуть или закрыть окно программы. В случае наступления перечисленных событий, будут вызваны соответствующие им обработчики событий (методы) и с окном вашей программы произойдут соответствующие метаморфозы.

Замечание, в программах листинги 10, 11, 12, 14 так же, следует последней строчкой добавить функцию mainloop(). Мы запускаем программы из среды IDLE и они работают без mainloop(), но, в любом случае, эту функцию необходимо добавить в конце своей программы если Вы используете виджеты из библиотеки tkinter.

Для создания бесконечного диалога программы с игроком, кроме бесконечного цикла mainloop() нам будет необходимо зарегистрировать событие "ввод текста в поле ввода ent закончен нажатием на клавишу Enter (Return)". Так же, к этому событию необходимо привязать обработчик события - функцию, которая будет вести диалог с игроком в поле msg2.

from tkinter import *                           # Импортируем все функции из библиотеки TKinter
from  random import choice                      # Импортируем функцию choice из библиотеки random

def play(event):                                # Функция, созданная пользователем
    '''
    Функция которая получает число из поля ввода от игрока,
    сравнивает каждую цифру введённую игроком с загаданным числом x,
    и выводит результаты в окно сообщений msg.
    '''
    msg2.config(text='Вы ввели число: ' + ent.get())

tk = Tk()                                       # Создаём окно пиложения
tk.geometry('500x350')                          # Задаём размер окна пиложения в пикселях
tk.title('Быки и Коровы')                       # Название программы в заголовке окна

msg = Message(width=400, padx=10, pady=5,
              text='Введите следующее число от 1023 до 9876 '
              'такое, чтобы цифры, составляющие это число,'
              ' не повторялись!')               # Создаём окно сообщений
msg.pack(padx=10, pady=5)                       # Размещаем окно сообщений в окне tk
msg.config(bg='light grey', fg='olive',
           font=('times', 12, 'italic'))        # Параметры для msg

ent = Entry(width=4)                            # Создаём поле ввода
ent.pack()                                      # Размещаем поле ввода в окне tk
ent.focus()                                     # Поместить фокус в поле ввода
ent.bind( '<Return>', play)                     # Опеределяем функцию play(),
                                                # как обработчик события нажатия
                                                # на клавишу Ввод (Enter)

msg2 = Message(width=400, padx=10, pady=5,
              text='Бык - цифра на своём месте.\n'
               'Корова - цифра не на своём месте.')     # Создаём окно сообщений
msg2.pack(padx=10, pady=5)                      # Размещаем окно сообщений в окне tk
msg2.config(font=('times', 12, 'normal'))       # Параметры для msg

mainloop()                                      # Цикл ожидания событий

Лист. 15. Создание GUI интерфейса для игры «Быки и Коровы».

В программе листинг 15, с помощью ключевого слова def мы создали новую функцию play.

Функции, которые создаёт программист, называют функциями определёнными пользователем (UDF User-defined function).

В определении функции play, в круглых скобках указан передаваемый в функцию параметр параметр event. Event - зарезервированное слово, переводится как событие. Через параметр event в функцию передаются параметры события. Наша функция play будет в нашей программе выполнять роль обработчика события "нажатие на клавишу Enter (Return) в поле ent".

В программе листинг 15 мы еще не прописали в создаваемой нами функции play всех действий, которые она должна будет выполнять. Пока функция play является заглушкой, позволяющей тестировать остальную часть кода программы. В функцию play мы добавили только метод  config поля msg2, который выведет в поле msg2 сообщение и число, которое мы ввели в поле ent.

Возвращает в программу строку, которую мы ввели в поле ввода, метод get(), который мы применяем к объекту класса Entry (поле ввода). В нашей программе мы написали ent.get(). К полю именем ent применили метод get().

В программе листинг 15, в созданной нами функции play, мы добавили описание этой функции. В Python описания создаваемых пользователем классов или функций заключают в тройные кавычки. Описания классов возвращает функция help(). Нам описание функции play очень пригодится для дальнейшей работы над созданием программного кода этой функции.

С помощью метода bind() зарегистрируем функцию play в качестве обработчика события происходящего с полем ввода ent. Первый параметр метода bind указывает при наступлении какого события вызывать обработчик. Имя функции, обработчика события без скобок указывают вторым параметром метода bind. См. листинг 15, строка ent.bind( '<Return>', play). Здесь указано событие '<Return>' - нажатие на клавишу Enter (Ввод). На старых клавиатурах на клавише "Ввод" чаще встречалась надпись "Return", иногда, "Enter" или вообще не было надписи, только стрелка влево, обозначающая перевод в начало следующей строки.

Рис. 5. Окно для игры «Быки и Коровы». 

Графический интерфейс игры «Быки и Коровы» почти готов. Пора добавить к нашей программе код отвечающий за логику игры.

Global, len()

Возьмём из программы листинг 7 блок кода, формирующий случайное четырёхзначное число и блок кода считающий быков и коров. Вставим эту часть программы листинг 7 в программу листинг 15 в функцию play. Заменим y=input() на y=ent.get(), а print(y + ' содержит ' + str(b) + ' быка и ' + str(c) + ' коровы') заменим на msg2.config(text='Ваше число: ' + y + ' содержит ' + str(b) + ' быка и ' + str(c) + ' коровы').

Теперь, при каждом вызове функции play, создаётся случайное число из четырёх цифр и в msg2 выводится количество быков и коров в числе, которое ввёл игрок в поле ent. Нам же, необходимо чтобы случайное число формировалось только 1 раз за всю игру. Способов сделать это существует много. Например, вынести блок кода, формирующий случайное четырёхзначное число в отдельную функцию. Мы предлагаем свой, оригинальный способ:

  1. За пределами функции play, создадим переменную x с значением равным пустой строке x = ''.
  2. Внутри функции play объявим переменную x глобальной global x,
  3. С помощью оператора if блок кода, формирующий случайное четырёхзначное число будет выполняться только тогда, когда переменная x содержит значение равное пустой строке if len(x) == 0.

Функция len() возвращает длину строки x. Функция len() может подсчитать количество любых объектов, которые содержит аргумент, переданный в эту функцию в качестве параметра.

Как это работает? Когда игрок первый раз введёт в поле ent своё число и нажмёт клавишу "Ввод", первый раз будет вызвана функция play. В этом случае, переменная x ещё содержит пустую строку, поэтому операция сравнения len(x) == 0 вернёт True (истина) и будет выполнен блок кода, формирующий случайное четырёхзначное число. Теперь, при следующем вызове функции play, len(x) будет возвращать 4, а len(x) == 0 вернёт False (ложь) и блок кода, формирующий случайное четырёхзначное число выполняться не будет.

Ключевое слово global объявляет глобальную переменную. Глобальная переменная, объявленная вне функции, будет доступна внутри функции, если она объявлена в этой функции с помощью ключевого слова global.

from tkinter import *                           # Импортируем все функции из библиотеки TKinter
from  random import choice                      # Импортируем функцию choice из библиотеки random

x = ''                                          # Создаём строку x для случайного числа

def play(event):                                # Функция, созданная пользователем
    '''
    Функция которая получает число из поля ввода от игрока,
    сравнивает каждую цифру введённую игроком с загаданным числом x,
    и выводит результаты в окно сообщений msg.
    '''
    global x                                    # Использовать глобальную переменную x
    if len(x) == 0:                             # Если строка x пустая,
        z = '0123456789'                        # Создаём строку z
        x = choice(z[1:10])                     # Создаём строку x из одного случайно выбранного символа без 0
        for i in range(3):                      # В цикле из 3-x повторений
            z = ''.join(z.split(x[i]))          # удаляем из строки z символ который добавили в строку x,
            x += choice(z)                      # добавляем к строке x случайно выбранные символы из строки z.
    y = ent.get()                               # введённая игроком строка
    b = 0; c = 0                                # Создаём переменные Bulls и Cows
    for i in range(4):                          # В цикле из 4-x повторений
        if x[i] == y[i]:                        # проверяем цифра на своём месте,
            b += 1                              # если да, то добавляем быка,
        elif y[i] in x:                         # если нет, проверяем есть ли в загаданном числе эта цифра?
            c += 1                              # если да, то добавляем корову
            
    msg2.config(text = 'Ваше число: ' + y + ' содержит ' + str(b) + ' быка и ' + str(c) + ' коровы')

tk = Tk()                                       # Создаём окно пиложения
tk.geometry('500x350')                          # Задаём размер окна пиложения в пикселях
tk.title('Быки и Коровы')                       # Название программы в заголовке окна

msg = Message(width=400, padx=10, pady=5,
              text='Введите следующее число от 1023 до 9876 '
              'такое, чтобы цифры, составляющие это число,'
              ' не повторялись!')               # Создаём окно сообщений
msg.pack(padx=10, pady=5)                       # Размещаем окно сообщений в окне tk
msg.config(bg='light grey', fg='olive',
           font=('times', 12, 'italic'))        # Параметры для msg

ent = Entry(width=4)                            # Создаём поле ввода
ent.pack()                                      # Размещаем поле ввода в окне tk
ent.focus()                                     # Поместить фокус в поле ввода
ent.bind( '<Return>', play)                     # Опеределяем функцию play(),
                                                # как обработчик события нажатия
                                                # на клавишу Ввод (Enter)

msg2 = Message(width=400, padx=10, pady=5,
              text='Бык - цифра на своём месте.\n'
               'Корова - цифра не на своём месте.')     # Создаём окно сообщений
msg2.pack(padx=10, pady=5)                      # Размещаем окно сообщений в окне tk
msg2.config(font=('times', 12, 'normal'))       # Параметры для msg

mainloop()                                      # Цикл ожидания событий

Лист. 16. Создание GUI интерфейса для игры «Быки и Коровы».

Рис. 6. Окно для игры «Быки и Коровы».

В программе листинг 16 обнаружилось 2 недостатка:

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

Delete(), cget()

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

from tkinter import *                           # Импортируем все функции из библиотеки TKinter
from  random import choice                      # Импортируем функцию choice из библиотеки random

x = ''                                          # Создаём строку x для случайного числа

def play(event):                                # Функция, созданная пользователем
    '''
    Функция которая получает число из поля ввода от игрока,
    сравнивает каждую цифру введённую игроком с загаданным числом x,
    и выводит результаты в окно сообщений msg.
    '''
    global x                                    # Использовать глобальную переменную x
    if len(x) == 0:                             # Если строка x пустая,
        z = '0123456789'                        # Создаём строку z
        x = choice(z[1:10])                     # Создаём строку x из одного случайно выбранного символа без 0
        for i in range(3):                      # В цикле из 3-x повторений
            z = ''.join(z.split(x[i]))          # удаляем из строки z символ который добавили в строку x,
            x += choice(z)                      # добавляем к строке x случайно выбранные символы из строки z.
        msg2.config(text='')                    # очищаем окно сообщений msg2
    y = ent.get()                               # введённая игроком строка
    b = 0; c = 0                                # Создаём переменные Bulls и Cows
    for i in range(4):                          # В цикле из 4-x повторений
        if x[i] == y[i]:                        # проверяем цифра на своём месте,
            b += 1                              # если да, то добавляем быка,
        elif y[i] in x:                         # если нет, проверяем есть ли в загаданном числе эта цифра?
            c += 1                              # если да, то добавляем корову
            
    msg2.config(text = msg2.cget('text') + 'Ваше число: ' + y + ' содержит ' + str(b) + ' быка и ' + str(c) + ' коровы\n')
    ent.delete(0, END)                          # очищаем поле ввода ent

tk = Tk()                                       # Создаём окно пиложения
tk.geometry('500x350')                          # Задаём размер окна пиложения в пикселях
tk.title('Быки и Коровы')                       # Название программы в заголовке окна

msg = Message(width=400, padx=10, pady=5,
              text='Введите следующее число от 1023 до 9876 '
              'такое, чтобы цифры, составляющие это число,'
              ' не повторялись!')               # Создаём окно сообщений
msg.pack(padx=10, pady=5)                       # Размещаем окно сообщений в окне tk
msg.config(bg='light grey', fg='olive',
           font=('times', 12, 'italic'))        # Параметры для msg

ent = Entry(width=4)                            # Создаём поле ввода
ent.pack()                                      # Размещаем поле ввода в окне tk
ent.focus()                                     # Поместить фокус в поле ввода
ent.bind( '<Return>', play)                     # Опеределяем функцию play(),
                                                # как обработчик события нажатия
                                                # на клавишу Ввод (Enter)

msg2 = Message(width=400, padx=10, pady=5,
              text='Бык - цифра на своём месте.\n'
               'Корова - цифра не на своём месте.')     # Создаём окно сообщений
msg2.pack(padx=10, pady=5)                      # Размещаем окно сообщений в окне tk
msg2.config(font=('times', 12, 'normal'))       # Параметры для msg

mainloop()                                      # Цикл ожидания событий

Лист. 17. Создание GUI интерфейса для игры «Быки и Коровы».

В программе листинг 17 в функции play() с помощью метода delete() после каждого хода очищается поле ent.

Метод delete() объекта класса Entry очищает поле ввода. Первый аргумент метода delete() указывает от какого по счёту символа очищать поле, а второй аргумент указывает до какого по счёту символа очищать это поле. Если используется зарезервированное слово END в качестве второго аргумента то поле очищается до конца.

Чтобы сохранить в объекте msg2 количество быков и коров в каждой попытке игрока угадать число, мы с помощью метода cget() с параметром 'text' извлекаем из объекта msg2 текстовую строку с прежним текстом и прибавляем к этой строке количество быков и коров в текущей попытке:

msg2.config(text = msg2.cget('text') + 'Ваше число: ' + y + ' содержит ' + str(b) + ' быка и ' + str(c) + ' коровы\n')

Рис. 7. Окно для игры «Быки и Коровы».

Добавляем счётчик ходов.

from tkinter import *                           # Импортируем все функции из библиотеки TKinter
from  random import choice                      # Импортируем функцию choice из библиотеки random

x = ''                                          # Создаём строку x для случайного числа
n = 0                                           # Счётчик ходов

def play(event):                                # Функция, созданная пользователем
    '''
    Функция которая получает число из поля ввода от игрока,
    сравнивает каждую цифру введённую игроком с загаданным числом x,
    и выводит результаты в окно сообщений msg.
    '''
    global x, n                                 # Использовать глобальную переменную x и n
    if len(x) == 0:                             # Если строка x пустая,
        z = '0123456789'                        # Создаём строку z
        x = choice(z[1:10])                     # Создаём строку x из одного случайно выбранного символа без 0
        for i in range(3):                      # В цикле из 3-x повторений
            z = ''.join(z.split(x[i]))          # удаляем из строки z символ который добавили в строку x,
            x += choice(z)                      # добавляем к строке x случайно выбранные символы из строки z.
        msg2.config(text='')                    # очищаем окно сообщений msg2
    y=ent.get()                                 # введённая игроком строка
    b = 0; c = 0                                # Создаём переменные Bulls и Cows
    for i in range(4):                          # В цикле из 4-x повторений
        if x[i] == y[i]:                        # проверяем цифра на своём месте,
            b += 1                              # если да, то добавляем быка,
        elif y[i] in x:                         # если нет, проверяем есть ли в загаданном числе эта цифра?
            c += 1                              # если да, то добавляем корову
    n += 1                                      # увеличиваем счётчик ходов       
    msg2.config(text=msg2.cget('text') + str(n) + '. Ваше число: ' + y + ' содержит ' + str(b) + ' быка и ' + str(c) + ' коровы\n')
    ent.delete(0, END)                          # очищаем поле ввода ent

tk = Tk()                                       # Создаём окно пиложения
tk.geometry('500x350')                          # Задаём размер окна пиложения в пикселях
tk.title('Быки и Коровы')                       # Название программы в заголовке окна

msg = Message(width=400, padx=10, pady=5,
              text='Введите следующее число от 1023 до 9876 '
              'такое, чтобы цифры, составляющие это число,'
              ' не повторялись!')               # Создаём окно сообщений
msg.pack(padx=10, pady=5)                       # Размещаем окно сообщений в окне tk
msg.config(bg='light grey', fg='olive',
           font=('times', 12, 'italic'))        # Параметры для msg

ent = Entry(width=4)                            # Создаём поле ввода
ent.pack()                                      # Размещаем поле ввода в окне tk
ent.focus()                                     # Поместить фокус в поле ввода
ent.bind( '<Return>', play)                     # Опеределяем функцию play(),
                                                # как обработчик события нажатия
                                                # на клавишу Ввод (Enter)

msg2 = Message(width=400, padx=10, pady=5,
              text='Бык - цифра на своём месте.\n'
               'Корова - цифра не на своём месте.')     # Создаём окно сообщений
msg2.pack(padx=10, pady=5)                      # Размещаем окно сообщений в окне tk
msg2.config(font=('times', 12, 'normal'))       # Параметры для msg

mainloop()                                      # Цикл ожидания событий

Лист. 18. Создание GUI интерфейса для игры «Быки и Коровы».

Рис. 8. Окно для игры «Быки и Коровы».

Программа листинг 18  не контролирует окончание игры, не фиксирует победу.

from tkinter import *                           # Импортируем все функции из библиотеки TKinter
from  random import choice                      # Импортируем функцию choice из библиотеки random

x = ''                                          # Создаём строку x для случайного числа
n = 0                                           # Счётчик ходов

def play(event):
    '''
    Функция которая получает число из поля ввода от игрока,
    сравнивает каждую цифру введённую игроком с загаданным числом x,
    и выводит результаты в окно сообщений msg.
    '''
    global x, n                                 # Использовать глобальную переменную x и n
    if len(x) == 0:                             # Если строка x пустая,
        z = '0123456789'                        # Создаём строку z
        x = choice(z[1:10])                     # Создаём строку x из одного случайно выбранного символа без 0
        for i in range(3):                      # В цикле из 3-x повторений
            z = ''.join(z.split(x[i]))          # удаляем из строки z символ который добавили в строку x,
            x += choice(z)                      # добавляем к строке x случайно выбранные символы из строки z.
        msg2.config(text='')                    # очищаем окно сообщений msg2
    y=ent.get()                                 # введённая игроком строка
    b = 0; c = 0                                # Создаём переменные Bulls и Cows
    for i in range(4):                          # В цикле из 4-x повторений
        if x[i] == y[i]:                        # проверяем цифра на своём месте,
            b += 1                              # если да, то добавляем быка,
        elif y[i] in x:                         # если нет, проверяем есть ли в загаданном числе эта цифра?
            c += 1                              # если да, то добавляем корову
    n += 1                                      # увеличиваем счётчик ходов       
    msg2.config(text=msg2.cget('text') + str(n) + '. Ваше число: ' + y + ' содержит ' + str(b) + ' быка и ' + str(c) + ' коровы\n')
    ent.delete(0, END)                          # очищаем окно ввода ent
    if b == 4:                                  # В случае победы:
        n = 0                                   # очистить счётчик ходов,
        x = ''                                  # очистить строку x для случайного числа.
        msg2.config(text='Бык - цифра на своём месте.\n'
               'Корова - цифра не на своём месте.')     # Выводим правила игры в окно сообщений

tk = Tk()                                       # Создаём окно пиложения
tk.geometry('500x350')                          # Задаём размер окна пиложения в пикселях
tk.title('Быки и Коровы')                       # Название программы в заголовке окна

msg = Message(width=400, padx=10, pady=5,
              text='Введите следующее число от 1023 до 9876 '
              'такое, чтобы цифры, составляющие это число,'
              ' не повторялись!')               # Создаём окно сообщений
msg.pack(padx=10, pady=5)                       # Размещаем окно сообщений в окне tk
msg.config(bg='light grey', fg='olive',
           font=('times', 12, 'italic'))        # Параметры для msg

ent = Entry(width=4)                            # Создаём поле ввода
ent.pack()                                      # Размещаем поле ввода в окне tk
ent.focus()                                     # Поместить фокус в поле ввода
ent.bind( '<Return>', play)                     # Опеределяем функцию play(),
                                                # как обработчик события нажатия
                                                # на клавишу Ввод (Enter)

msg2 = Message(width=400, padx=10, pady=5,
              text='Бык - цифра на своём месте.\n'
               'Корова - цифра не на своём месте.')     # Создаём окно сообщений
msg2.pack(padx=10, pady=5)                      # Размещаем окно сообщений в окне tk
msg2.config(font=('times', 12, 'normal'))       # Параметры для msg

Лист. 19. Создание GUI интерфейса для игры «Быки и Коровы».

Рис. 9. Окно для игры «Быки и Коровы», когда игрок нашёл искомое число.

В программе листинг 19, в случае победы, игра начинается с начала, см рис. 9. После того как игрок нашёл искомое число, хотелось бы добавить в программу возможность выбора начать игру с начала или закончить игру. Традиционно, это делается с помощью всплывающего окна диалога. 

 Dialog(), exit() 

from tkinter import *                           # Импортируем все функции из библиотеки TKinter
from tkinter.dialog import *                    # Импортируем класс dialog для создания окна диалога
from  random import choice                      # Импортируем функцию choice из библиотеки random

x = ''                                          # Создаём строку x для случайного числа
n = 0                                           # Счётчик ходов

def play(event):
    '''
    Функция которая получает число из поля ввода от игрока,
    сравнивает каждую цифру введённую игроком с загаданным числом x,
    и выводит результаты в окно сообщений msg.
    '''
    global x, n                                 # Использовать глобальную переменную x и n
    if len(x) == 0:                             # Если строка x пустая,
        z = '0123456789'                        # Создаём строку z
        x = choice(z[1:10])                     # Создаём строку x из одного случайно выбранного символа без 0
        for i in range(3):                      # В цикле из 3-x повторений
            z = ''.join(z.split(x[i]))          # удаляем из строки z символ который добавили в строку x,
            x += choice(z)                      # добавляем к строке x случайно выбранные символы из строки z.
        msg2.config(text='')                    # очищаем окно сообщений msg2
    y = ent.get()                               # введённая игроком строка
    b = 0; c = 0                                # Создаём переменные Bulls и Cows
    for i in range(4):                          # В цикле из 4-x повторений
        if x[i] == y[i]:                        # проверяем цифра на своём месте,
            b += 1                              # если да, то добавляем быка,
        elif y[i] in x:                         # если нет, проверяем есть ли в загаданном числе эта цифра?
            c += 1                              # если да, то добавляем корову
    n += 1                                      # увеличиваем счётчик ходов       
    msg2.config(text=msg2.cget('text') + str(n) + '. Ваше число: ' + y + ' содержит ' + str(b) + ' быка и ' + str(c) + ' коровы\n')
    ent.delete(0, END)                          # очищаем окно ввода ent
    if b == 4:                                  # В случае победы,
        gameOwer = Dialog(title = 'Вы победили за ' + str(n) + ' ходов',
               text = '     Сыграем ещё?           ',
               bitmap = 'questhead',
               default = 0,
               strings = ('Да', 'Нет'))
        if gameOwer.num == 1: exit()            # Закончить игру
        n = 0                                   # Очистить счётчик ходов
        x = ''                                  # Очистить строку x для случайного числа
        msg2.config(text='Бык - цифра на своём месте.\n'
               'Корова - цифра не на своём месте.')     # Выводим правила игры в окно сообщений

tk = Tk()                                       # Создаём окно пиложения
tk.geometry('500x350')                          # Задаём размер окна пиложения в пикселях
tk.title('Быки и Коровы')                       # Название программы в заголовке окна

msg = Message(width=400, padx=10, pady=5,
              text='Введите следующее число от 1023 до 9876 '
              'такое, чтобы цифры, составляющие это число,'
              ' не повторялись!')               # Создаём окно сообщений
msg.pack(padx=10, pady=5)                       # Размещаем окно сообщений в окне tk
msg.config(bg='light grey', fg='olive',
           font=('times', 12, 'italic'))        # Параметры для msg

ent = Entry(width=4)                            # Создаём поле ввода
ent.pack()                                      # Размещаем поле ввода в окне tk
ent.focus()                                     # Поместить фокус в поле ввода
ent.bind( '<Return>', play)                     # Опеределяем функцию play(),
                                                # как обработчик события нажатия
                                                # на клавишу Ввод (Enter)

msg2 = Message(width=400, padx=10, pady=5,
              text='Бык - цифра на своём месте.\n'
               'Корова - цифра не на своём месте.')     # Создаём окно сообщений
msg2.pack(padx=10, pady=5)                      # Размещаем окно сообщений в окне tk
msg2.config(font=('times', 12, 'normal'))       # Параметры для msg

Лист. 20. Программа игры «Быки и Коровы» с GUI интерфейсом.

В программе листинг 20, как только игрок нашёл искомое число, всплывает окно диалога Сыграем ещё? Да / Нет. В заголовке окна диалога выводятся показатели сыгранной партии: “Вы победили за n ходов“. См. рис. 10.

Рис. 10. Так выглядит окно игры «Быки и Коровы», когда игрок нашёл искомое число.

В программе листинг 20 мы создаём окно диалога с помощью конструктора Dialog(). Чтобы воспользоваться конструктором Dialog(), мы в первых строках программы, импортируем функции из библиотеки tkinter.dialog.

Конструктор Dialog() создаёт всплывающее окно диалога, в нашем случае, с двумя кнопками, с вопросом, пиктограммой и заголовком окна. Все эти элементы окна диалога в нашей программе имеют функциональную нагрузку. 

Окно класса Dialog возвращает в программу номер нажатой кнопки. В нашей программе, номер нажатой в окне диалога проверяется и если он равен 1 (кнопка "Нет " ) , будет вызвана функция exit().

Функция exit() закрывает все окна и завершает работу программы. 

Если в окне диалога была нажата кнопка "Да",  будет сделана инициализация переменных x, n и обновлён текст в msg2. Далее, игра начнётся с начала. 

Игра «Быки и Коровы» развивает логическое мышление. Чем меньше мы сделаем ходов на поиск загаданного числа, тем лучше обстоят у нас дела с логикой. Но человеческий мозг, так же, обладает быстродействием. Чем меньше мы потратим времени на поиск загаданного числа, тем лучше.

Для замера времени, потраченного на поиск загаданного числа, введём в нашу программу счётчик времени.

Time(), int()

#!/usr/bin/python3
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width:$

# Bulls and Cows (Быки и Коровы)
# This is my version of the game, known as "Master-mind".
#
# Created on July 21, 2021.
# Author of this program code : Diorditsa A.
# I thank Sergey Polozkov for checking the code for hidden errors
# Dedicated to classmate Anya Lezhepekova
#
# BullsAndCows.py is distributed in the hope that it will be useful, but
# WITHOUT WARRANTY OF ANY KIND; not even an implied warranty
# MARKETABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See. See the GNU General Public License for more information.
# You can get a copy of the GNU General Public License
# by link http://www.gnu.org/licenses/

from tkinter import *                           # Импортируем все функции из библиотеки TKinter
from tkinter.dialog import *                    # Импортируем класс dialog для создания окна диалога
from random import choice                       # Импортируем функцию choice из библиотеки random
from time import *                              # Импортируем функции времени

x = ''                                          # Создаём строку x для случайного числа
n = 0                                           # Счётчик ходов
t = 0                                           # Счётчик времени

def play(event):                                # Функция, созданная пользователем
    '''
    Функция которая получает число из поля ввода от игрока,
    сравнивает каждую цифру введённую игроком с загаданным числом x,
    и выводит результаты в окно сообщений msg.
    '''
    global x, n, t                              # Использовать глобальную переменную x, n и t
    if len(x) == 0:                             # Если строка x пустая,
        z = '0123456789'                        # Создаём строку z
        x = choice(z[1:10])                     # Создаём строку x из одного случайно выбранного символа без 0
        for i in range(3):                      # В цикле из 3-x повторений
            z = ''.join(z.split(x[i]))          # удаляем из строки z символ который добавили в строку x,
            x += choice(z)                      # добавляем к строке x случайно выбранные символы из строки z,
        msg2.config(text='')                    # очищаем окно сообщений msg2,
        t = time()                              # запускаем счётчик времени.
    y = ent.get()                               # введённая игроком строка
    b = 0; c = 0                                # Создаём переменные Bulls и Cows
    for i in range(4):                          # В цикле из 4-x повторений
        if x[i] == y[i]:                        # проверяем цифра на своём месте,
            b += 1                              # если да, то добавляем быка,
        elif y[i] in x:                         # если нет, проверяем есть ли в загаданном числе эта цифра?
            c += 1                              # если да, то добавляем корову
    n += 1                                      # увеличиваем счётчик ходов       
    msg2.config(text = msg2.cget('text') + str(n) + '. Ваше число: ' + y + ' содержит ' + str(b) + ' быка и ' + str(c) + ' коровы\n')
    ent.delete(0, END)                          # очищаем окно ввода ent
    if b == 4:                                  # В случае победы,
        gameOwer = Dialog(title = 'Вы победили за ' + str(n) + ' ходов; ' + str(int(time()-t)) + ' сек.',
               text = '     Сыграем ещё?           ',
               bitmap = 'questhead',
               default = 0,
               strings = ('Да', 'Нет'))
        if gameOwer.num == 1: exit()            # Закончить игру
        n = 0                                   # Очистить счётчик ходов
        x = ''                                  # Очистить строку x для случайного числа
        msg2.config(text='Бык - цифра на своём месте.\n'
               'Корова - цифра не на своём месте.')     # Выводим правила игры в окно сообщений

tk = Tk()                                       # Создаём окно пиложения
tk.geometry('500x350')                          # Задаём размер окна пиложения в пикселях
tk.title('Быки и Коровы')                       # Название программы в заголовке окна

msg = Message(width=400, padx=10, pady=5,
              text='Введите следующее число от 1023 до 9876 '
              'такое, чтобы цифры, составляющие это число,'
              ' не повторялись!')               # Создаём окно сообщений
msg.pack(padx=10, pady=5)                       # Размещаем окно сообщений в окне tk
msg.config(bg='light grey', fg='olive',
           font=('times', 12, 'italic'))        # Параметры для msg

ent = Entry(width=4)                            # Создаём поле ввода
ent.pack()                                      # Размещаем поле ввода в окне tk
ent.focus()                                     # Поместить фокус в поле ввода
ent.bind( '<Return>', play)                     # Опеределяем функцию play(),
                                                # как обработчик события нажатия
                                                # на клавишу Ввод (Enter)

msg2 = Message(width=400, padx=10, pady=5,
              text='Бык - цифра на своём месте.\n'
               'Корова - цифра не на своём месте.')     # Создаём окно сообщений
msg2.pack(padx=10, pady=5)                      # Размещаем окно сообщений в окне tk
msg2.config(font=('times', 12, 'normal'))       # Параметры для msg

mainloop()                                      # Цикл ожидания событий

Лист. 21. Программа игры «Быки и Коровы» со счётчиком времени.

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

Функция time() возвращает время в секундах от начала эпохи с точностью до до 7-ми знаков после запятой.

В тот момент, когда игрок ввёл поле ввода своё первое число и нажал клавишу Enter, в переменной t программа сохраняет время возвращаемое функцией time(). В тот момент, когда игрок одержал победу, в title окна диалога выводится разность time() - t, это и есть время затраченное игроком на поиск числа, загаданного компьютером. 

Время, затраченное игроком на поиск числа вычисляется в секундах, с точностью до 7-ми знаков после запятой. Такая высокая точность нас не интересует, поэтому, используя функцию int() мы округляем значение времени до целых секунд отсекая цифры после запятой. 

Кроме того, в программу (листинг 21), в её заголовок, мы добавили большой блок комментариев, который содержит имя автора программы и лицензионное соглашение. Эта программа распространяется под стандартной GNU General Public лицензией. С полным текстом лицензионного соглашения можно ознакомиться на сайте http://www.gnu.org/licenses/

Рис. 11. Так выглядит окно игры «Быки и Коровы», когда игрок нашёл искомое число.

 

Моей однокласснице Ане Лежепёковой посвящаю.