Крестики-нолики на Python за 15 минут
Kivy - это открытая Python библиотека для быстрой разработки приложений. У нее много преимуществ, например, кросс-платформенность, открытость а также использование GPU для ускорения отрисовки графических интерфейсов.
Информацию об API вы можете найти тут.
Для начала работы нам потребуется библиотека kivy. Установить ее можно с помощью команды (Windows):
pip install kivy
Либо с помощью команды (Linux, Python 3):
pip3 install kivy
Импортируем необходимые нам элементы:
from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.uix.gridlayout import GridLayout from kivy.uix.button import Button from kivy.config import Config
С помощью Config
и функции set
зададим параметры нашего окна:
Config.set("graphics", "resizable", "0") Config.set("graphics", "width", "300") Config.set("graphics", "height", "300")
Мы установили размер окна 300х300 и указали что окно нельзя перерастягивать.
Нам нужно, чтобы при каждом новом ходе крестик сменялся ноликом и наоборот. Для этого создадим класс нашего приложения, и назовем его MainApp
. Оно должно наследоваться от класса kivy – App
. Внутри класса создадим переменную self.switch
, и определим ее в функции __init__
:
class MainApp(App): def __init__(self): self.switch = True super().__init__()
Строчка с super()
нужна для того, чтобы инициализировать родительский класс App
.
Внутри класса мы также объявим 3 функции: tic_tac_toe
, restart
и build
:
def tic_tac_toe(self): pass def restart(self): pass def build(self): pass
Поговорим подробнее о каждой из них.
tic_tac_toe
: функция, которая будет исполнять всю логику нашего приложения, с английского tic tac как раз и означает крестики-ноликиrestart
: функция, которую мы будем вызывать для того, чтобы перезапустить игру, по сути сбросив все значения кнопок и переменных до начального состоянияbuild
: важная функция, она определена в самомApp
, но ее поведение мы должны "перегрузить"
Начнем как раз с функции build
.
def build(self): self.title = "Крестики-нолики" root = BoxLayout(orientation="vertical", padding=5) grid = GridLayout(cols=3)
self.title
это название нашего окна, далее создадим объект BoxLayout
, он будет являться своеобразным контейнером для всех наших кнопок. Также создадим переменную GridLayout
, и укажем число колонок = 3. На рисунке ниже ее область будет показана красным цветом. После того, как отрисуем область сетки, добавим внизу кнопочку Restart (показана синим цветом).
self.buttons = []
После этого, в цикле мы добавим в лист 9 объектов класса Button
. Каждый такой объект имеет цвет, размер шрифта и другие переменные. При нажатии на такую кнопку вызывается метод tic_tac_toe
, о котором мы поговорим чуть позднее. В конце цикла не забываем добавить полученные в ходе тела цикла кнопки в наш лист self.buttons
, кроме того, добавляем новый виджет в grid
:
for _ in range(9): button = Button( color = [0,0,0,1], font_size = 24, disabled = False, on_press = self.tic_tac_toe ) self.buttons.append(button) grid.add_widget(button)
Также добавим виджет для кнопки Restart:
root.add_widget( Button( text = "Restart", size_hint = [1,.1], on_press = self.restart ) )
Весь код функции build
целиком:
def build(self): self.title = "Крестики-нолики" root = BoxLayout(orientation="vertical", padding=5) grid = GridLayout(cols=3) self.buttons = [] for _ in range(9): button = Button( color = [0,0,0,1], font_size = 24, disabled = False, on_press = self.tic_tac_toe ) self.buttons.append(button) grid.add_widget(button) root.add_widget(grid) root.add_widget( Button( text = "Restart", size_hint = [1,.1], on_press = self.restart ) ) return root
Поскольку мы добавили кнопку Restart, давайте поговорим об ее функционале. Добавим в функцию restart
следующий код:
def restart(self, arg): self.switch = True for button in self.buttons: button.color = [0,0,0,1] button.text = "" button.disabled = False
Переменная switch = True
показывает, что ход снова возвращается к крестикам, как и было указано в начале игры. Далее мы проходимся в цикле по всем кнопкам, возвращая им цвет по умолчанию и стирая текст. Модификатор disabled
показывает, что теперь вы снова можете нажать на кнопку.
Но вернемся к функции tic_tac_toe
. Для начала укажем снова глобальную переменную switch
. Как вы помните, она переключается в обратное положение каждый раз после нажатия кнопки, чем символизирует переключение хода на другого игрока. В этой функции аргумент arg
будет как раз являться нашим конкретным button
.
При нажатии на кнопку, согласно с логикой приложения, мы должны выключить возможность нажать на нее второй раз. Для этого необходимо указать arg.disabled = True
.
def tic_tac_toe(self, arg): arg.disabled = True
Теперь проверим в каком положении стоит наш self.switch
, и в зависимости от этого, определим переменную arg.text
крестиком либо ноликом:
def tic_tac_toe(self, arg): arg.disabled = True arg.text = 'X' if self.switch else 'O'
Фактически, выражение 'X' if self.switch else 'O'
означает 'X'
в случае если self.switch = True
и 'O'
в случае если self.switch = False
.
После этого, разумеется, мы должны переключить ход:
self.switch = not self.switch
Далее начнется самое сложное. Нам необходимо задать все возможные координаты кнопок, которые дадут выигрыш:
coordinate = ( (0,1,2),(3,4,5),(6,7,8), # по оси X (0,3,6),(1,4,7),(2,5,8), # по оси Y (0,4,8),(2,4,6), # по диагонали D )
Также зададим и возможные вектора самих нажатых кнопок (в них будет храниться буквы 'X'
либо 'O'
). Легче всего это сделать, написав короткую лямбда-функцию, которая будет возвращать список букв для каждого набора координат:
vector = lambda item: [self.buttons[x].text for x in item]
Такой объект является анонимной функцией, при выполнении которой возвратится список в котором будут храниться буквы. Если в таком списке все 3 буквы одинаковые, это означает выигрыш!
Определим переменную win = False
, а также цвет кнопок в результате выигрыша:
win = False color = [0,1,0,1] # Зеленый
Теперь напишем цикл, который будет определять победную комбинацию нажатых кнопок. В переменной coordinate
есть 8 комбинаций с победными сочетаниями кнопок. Пройдемся по ним в цикле:
for item in coordinate: if vector(item).count('X') == 3\\ or vector(item).count('O') == 3: win = True for i in item: self.buttons[i].color = color break
Если в таком векторе функция count
посчитает 3 раза буквы 'X'
либо 'O'
, значит игрок победил, в таком случае окрашиваем победную комбинацию кнопок в зеленый цвет.
Также необходимо залочить все кнопки в случае победы:
if win: for button in self.buttons: button.disabled = True
Внимательный читатель мог заметить, что переменная win в принципе и не нужна, а последний цикл можно переместить в цикл после перекрашивания цвета кнопок в зеленый. Полный код функции tic_tac_toe
после рефакторинга:
def tic_tac_toe(self, arg): arg.disabled = True arg.text = 'X' if self.switch else 'O' self.switch = not self.switch coordinate = ( (0,1,2),(3,4,5),(6,7,8), # X (0,3,6),(1,4,7),(2,5,8), # Y (0,4,8),(2,4,6), # D ) vector = lambda item: [self.buttons[x].text for x in item] color = [0, 1, 0, 1] for item in coordinate: if vector(item).count('X') == 3 \\ or vector(item).count('O') == 3: for i in item: self.buttons[i].color = color for button in self.buttons: button.disabled = True break
Весь код целиком — https://pastebin.com/8iutyKKw
Материал подготовлен образовательной организацией Python Academy.