Создание сапера при помощи модуля Tkinter

от автора

День добрый. Почти каждый начинающий программист стремится к созданию своей первой игры. Спустя пол года ленивого кропотливого обучения я решился написать сапера. Языком написания был выбран Python, модулем для добавления интерфейса tkinter, потому как уже имелся опыт работы с ним. Этот пост будет полезен скорее начинающим кодерам, но если вы итак все знаете, можете написать свои советы по улучшению кода в комменты.

Приступим. Первым делом нужно было определиться, что будет собой представлять клетка. Самое выгодное решение — создать класс поля:

class pole(object): #создаем Класс поля, наследуемся от Object     def __init__(self,master,row, column): #Инициализация поля. master - окно Tk().         self.button = Button(master, text = '   ') #Создаем для нашего поля атрибут 'button'         self.mine = False #Переменная наличия мины в поле         self.value = 0 #Кол-во мин вокруг         self.viewed = False #Открыто/закрыто поле         self.flag = 0 #0 - флага нет, 1 - флаг стоит, 2 - стоит "?"         self.around = [] #Массив, содержащий координаты соседних клеток         self.clr = 'black' #Цвет текста         self.bg = None #Цвет фона         self.row = row #Строка         self.column = column #Столбец 

Теперь надо создать интерфейс для настройки игры:

settings = Tk() #Создаем окно settings.title('Настройки') #Пишем название окна settings.geometry('200x150') #Задаем размер mineText = Text(settings, width = 5, height = 1) #Создаем поля для ввода текста и пояснения mineLabe = Label(settings, height = 1, text = 'Бомбы:') highText = Text(settings, width = 5, height = 1) highLabe = Label(settings, height = 1, text = 'Ширина:') lenghtText = Text(settings, width = 5, height = 1) lenghtLabe = Label(settings, height = 1, text = 'Высота:') mineBut = Button(settings, text = 'Начать:', command = bombcounter) #Создаем кнопку mineBut.place(x = 70, y = 90)  #Размещаем это все mineText.place(x = 75, y = 5) mineLabe.place(x = 5, y = 5) highText.place(x = 75, y = 30) highLabe.place(x = 5, y = 30) lenghtText.place(x = 75, y = 55) lenghtLabe.place(x = 5, y = 55) settings.mainloop()  

В итоге получаем вот такое вот окно:

image

Теперь нужно прописать функцию bombcounter

def bombcounter():      global bombs      if mineText.get('1.0', END) == '\n': #Проверяем наличие текста         bombs = 10 #Если текста нет, то по стандарту кол-во бомб - 10     else:         bombs = int(mineText.get('1.0', END)) #Если текст есть, то это и будет кол-во бомб     if highText.get('1.0', END) == '\n':         high = 9     else:         high = int(highText.get('1.0', END))     if lenghtText.get('1.0', END) == '\n':         lenght = 9     else:         lenght = int(lenghtText.get('1.0', END))     game(high,lenght) #Начинаем игру, передавая кол-во полей 

Теперь приступаем к основной части, написанию функции игры:

def game(high,lenght): #получаем значения     root = Tk()      root.title('Сапер')      global buttons     global mines     global flags     flags = [] #Массив, содержащий в себе места, где стоят флажки     mines = [] #Массив, содержащий в себе места, где лежат мины     buttons = [[pole(root,j,i) for i in range(high)] for j in range(lenght)] #Двумерный массив, в котором лежат поля     for i in range(len(buttons)): #Цикл по строкам         for j in range(len(buttons[i])): #Цикл по элементам строки             buttons[i][j].button.grid(column = j, row = i, ipadx = 7, ipady = 1) #Размещаем все в одной сетке при помощи grid             buttons[i][j].button.bind('<Button-1>', buttons[i][j].view) #Биндим открывание клетки             buttons[i][j].button.bind('<Button-3>', buttons[i][j].setFlag) #Установка флажка             buttons[i][j].setAround() #Функция заполнения массива self.around     buttons[0][0].button.bind('<Control-Button-1>', cheat) #создаем комбинацию клавиш для быстрого решения     root.resizable(False,False) #запрещаем изменения размера     root.mainloop() 

У нас появилось целых три функции, которые нужно написать. Начнем с .setAround():

    def setAround(self):         if self.row == 0:             self.around.append([self.row+1,self.column])             if self.column == 0:                 self.around.append([self.row,self.column+1])                 self.around.append([self.row+1,self.column+1])             elif self.column == len(buttons[self.row])-1:                 self.around.append([self.row,self.column-1])                 self.around.append([self.row+1,self.column-1])             else:                 self.around.append([self.row,self.column-1])                 self.around.append([self.row,self.column+1])                 self.around.append([self.row+1,self.column+1])                 self.around.append([self.row+1,self.column-1])         elif self.row == len(buttons)-1:             self.around.append([self.row-1,self.column])             if self.column == 0:                 self.around.append([self.row,self.column+1])                 self.around.append([self.row-1,self.column+1])             elif self.column == len(buttons[self.row])-1:                 self.around.append([self.row,self.column-1])                 self.around.append([self.row-1,self.column-1])             else:                 self.around.append([self.row,self.column-1])                 self.around.append([self.row,self.column+1])                 self.around.append([self.row-1,self.column+1])                 self.around.append([self.row-1,self.column-1])         else:             self.around.append([self.row-1,self.column])             self.around.append([self.row+1,self.column])             if self.column == 0:                 self.around.append([self.row,self.column+1])                 self.around.append([self.row+1,self.column+1])                 self.around.append([self.row-1,self.column+1])             elif self.column == len(buttons[self.row])-1:                 self.around.append([self.row,self.column-1])                 self.around.append([self.row+1,self.column-1])                 self.around.append([self.row-1,self.column-1])             else:                 self.around.append([self.row,self.column-1])                 self.around.append([self.row,self.column+1])                 self.around.append([self.row+1,self.column+1])                 self.around.append([self.row+1,self.column-1])                 self.around.append([self.row-1,self.column+1])                 self.around.append([self.row-1,self.column-1]) 

Все, что здесь происходит, это заполнение массива self.around. Мы рассматриваем различные случаи и на выходе получаем верный ответ. Если есть варианты, как сделать это проще, я приму их во внимание.

Пишем view()

    def view(self,event):         if mines == []: #При первом нажатии             seter(0,self.around,self.row,self.column) #Устанавливаем мины         if self.value == 0: #Устанавливаем цвета. Можно написать и для 6,7 и 8, но у меня закончилась фантазия             self.clr = 'yellow'             self.value = None             self.bg = 'lightgrey'         elif self.value == 1:             self.clr = 'green'         elif self.value == 2:             self.clr = 'blue'         elif self.value == 3:             self.clr = 'red'         elif self.value == 4:             self.clr = 'purple'                  if self.mine and not self.viewed and not self.flag: #Если в клетке есть мина, она еще не открыта и на ней нет флага             self.button.configure(text = 'B', bg = 'red') #Показываем пользователю, что тут есть мина             self.viewed = True #Говорим, что клетка раскрыта             for q in mines:                 buttons[q[0]][q[1]].view('<Button-1>') #Я сейчас буду вскрывать ВСЕ мины             lose() #Вызываем окно проигрыша                  elif not self.viewed and not self.flag: #Если мины нет, клетка не открыта и флаг не стоит             self.button.configure(text = self.value, fg = self.clr, bg = self.bg) #выводим в текст поля значение             self.viewed = True             if self.value == None: #Если вокруг нет мин                 for k in self.around:                     buttons[k[0]][k[1]].view('<Button-1>') #Открываем все поля вокруг  

Итак. Сейчас у нас написаны функции: открытия клетки, заполнения массива around, начала игры и получения значения насчет размера игрового поля и кол-ва мин. Но до сих пор нет функции для установки мин. Исправляемся:

def seter(q, around,row,column): #Получаем массив полей вокруг и координаты нажатого поля     if q == bombs: #Если кол-во установленных бомб = кол-ву заявленных         for i in range(len(buttons)): #Шагаем по строкам             for j in range(len(buttons[i])): #Шагаем по полям в строке i                 for k in buttons[i][j].viewAround(): #Шагаем по полям вокруг выбранного поля j                     if buttons[k[0]][k[1]].viewMine(): #Если в одном из полей k мина                         buttons[i][j].setValue(buttons[i][j].viewValue()+1) #То увеличиваем значение поля j         return     a = choice(buttons) #Выбираем рандомную строку     b = choice(a) #Рандомное поле     if [buttons.index(a),a.index(b)] not in mines and [buttons.index(a),a.index(b)] not in around and [buttons.index(a),a.index(b)] != [row,column]: #Проверяем, что выбранное поле не выбиралось до этого и, что не является полем на которую мы нажали (или окружающим ее полем)         b.setMine() #Ставим мину         mines.append([buttons.index(a),a.index(b)]) #Добавляем ее в массив          seter(q+1,around,row,column) #Вызываем установщик, сказав, что одна мина уже есть     else:         seter(q,around,row,column) #Вызываем установщик еще раз 

И вторая важная для нас функция: setValue()

    def setValue(self,value):         self.value = value 

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

    def setFlag(self,event):         if self.flag == 0 and not self.viewed: #Если поле не открыто и флага нет             self.flag = 1 #Ставим флаг             self.button.configure(text = 'F', bg = 'yellow') #Выводим флаг             flags.append([self.row,self.column]) #Добавляем в массив флагов         elif self.flag == 1: #Если флаг стоим             self.flag = 2 #Ставим значение '?'             self.button.configure(text = '?', bg = 'blue') #Выводим его             flags.pop(flags.index([self.row,self.column])) #Удаляем флаг из массива флагов         elif self.flag == 2: #Если вопрос             self.flag = 0 #Устанавливаем на отсутствие флага             self.button.configure(text = '   ', bg = 'white') #Выводим пустоту         if sorted(mines) == sorted(flags) and mines != []: #если массив флагов идентичен массиву мин             winer() #Сообщаем о победе 

Функции lose() и winer() просты и не требуют объяснений. Если будет нужно, напишу в комменты.

Финальный вид:

image

Свои вопросы, предложения и критику пишите в комменты, постараюсь ответить, обсудить и подумать.
ссылка на оригинал статьи https://habrahabr.ru/post/326358/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *