Написать игру Сапёр (Mines) на Python с использованием библиотеки Tkinter оказалось не сложно. В этой статье покажем все этапы создания этой игры, которая массово была представлена публике в Windows 95. Следует отметить, что в Windows 3.11 игра winmine уже была. Но кто теперь это вспомнит?

Правила игры:

Воспользуемся шаблоном из кнопок для игр на поле в клетку.

from tkinter import *               # графическая библиотека
from random import shuffle          # перемешать список suffle(A)
column = 3                          # столбцы
row = 3                             # строки
btn = []
playground = list('ЧТОЕСТЖУ ')      # виртуальное игровое поле

def play(n):                        # функция обработчик нажатия на кнопку
    btn[n].config(text=n)

for i in range(row):
    f = Frame()                     # Фрейм
    f.pack(expand=YES, fill=BOTH)   # Вывод фрейма на экран
    for j in range(column):
        n = i * column + j
        btn += [Button(f)]
        btn[n].pack(expand=YES, fill=BOTH, side=LEFT)
        btn[n].config(width=3, height=2)
        btn[n].config(text=playground[n])
        btn[n].config(command=lambda n=n: play(n))

mainloop()                          # главный цикл программы

Лист. 1. Шаблон для игр на поле в клетку.

from tkinter import *               # графическая библиотека
from random import shuffle          # перемешать список suffle(A)
column = 5                          # столбцы
row = 5                             # строки
btn = []
playground = [1,0,0,0,0] * (row*column//5+1)      # виртуальное игровое поле
shuffle(playground)

def play(n):                        # функция обработчик нажатия на кнопку
    btn[n].config(text=n)

for i in range(row):
    f = Frame()                     # Фрейм
    f.pack(expand=YES, fill=BOTH)   # Вывод фрейма на экран
    for j in range(column):
        n = i * column + j
        btn += [Button(f)]
        btn[n].pack(expand=YES, fill=BOTH, side=LEFT)
        btn[n].config(width=3, height=2)
        btn[n].config(text=playground[n])
        btn[n].config(command=lambda n=n: play(n))

mainloop()                          # главный цикл программы

Лист. 2. Создан и перемешан список playground.

from tkinter import *               # графическая библиотека
from random import shuffle          # перемешать список suffle(A)
column = 5                          # столбцы
row = 5                             # строки
btn = []
playground = [1,0,0,0,0] * (row*column//5+1)      # виртуальное игровое поле
shuffle(playground)

def play(n):                        # функция обработчик нажатия на кнопку
    if playground[n] == 1:
        btn[n].config(text='M')

for i in range(row):
    f = Frame()                     # Фрейм
    f.pack(expand=YES, fill=BOTH)   # Вывод фрейма на экран
    for j in range(column):
        n = i * column + j
        btn += [Button(f)]
        btn[n].pack(expand=YES, fill=BOTH, side=LEFT)
        btn[n].config(width=3, height=2)
        btn[n].config(text=playground[n])
        btn[n].config(command=lambda n=n: play(n))

mainloop()                          # главный цикл программы

Лист. 3. В функции play вычисляется попадание на мину. 

from tkinter import *               # графическая библиотека
from random import shuffle          # перемешать список suffle(A)
column = 5                          # столбцы
row = 5                             # строки
btn = []
playground = [1,0,0,0,0] * (row*column//5+1)      # виртуальное игровое поле
shuffle(playground)

def play(n):                        # функция обработчик нажатия на кнопку
    m = 0
    if playground[n] == 1:
        btn[n].config(text='M')
        btn[n].config(bg='#f88', activebackground='#f66')
    elif m == 0:
        btn[n].config(bg='#afa', activebackground='#7f7')
    else :
        btn[n].config(text=m)

for i in range(row):
    f = Frame()                     # Фрейм
    f.pack(expand=YES, fill=BOTH)   # Вывод фрейма на экран
    for j in range(column):
        n = i * column + j
        btn += [Button(f)]
        btn[n].pack(expand=YES, fill=BOTH, side=LEFT)
        btn[n].config(width=3, height=2)
        btn[n].config(text=playground[n])
        btn[n].config(command=lambda n=n: play(n))

mainloop()                          # главный цикл программы

Лист. 4. Определена логика работы функции play и добавлен цвет.

from tkinter import *               # графическая библиотека
from random import shuffle          # перемешать список suffle(A)
column = 5                          # столбцы
row = 5                             # строки
btn = []
playground = [1,0,0,0,0] * (row*column//5+1)      # виртуальное игровое поле
shuffle(playground)

def play(n):                        # функция обработчик нажатия на кнопку
    m = (playground[n+1] + playground[n-1] +
         playground[n+column] + playground[n-column] +
         playground[n+column+1] + playground[n+column-1] +
         playground[n-column+1] + playground[n-column-1])
    if playground[n] == 1:
        btn[n].config(text='M')
        btn[n].config(bg='#f88', activebackground='#f66')
    elif m == 0:
        btn[n].config(bg='#afa', activebackground='#7f7')
    else :
        btn[n].config(text=m)

for i in range(row):
    f = Frame()                     # Фрейм
    f.pack(expand=YES, fill=BOTH)   # Вывод фрейма на экран
    for j in range(column):
        n = i * column + j
        btn += [Button(f)]
        btn[n].pack(expand=YES, fill=BOTH, side=LEFT)
        btn[n].config(width=3, height=2)
        btn[n].config(text=playground[n])
        btn[n].config(command=lambda n=n: play(n))

mainloop()                          # главный цикл программы

Лист. 5. В функции play B добавлен расчёт количества мин вокруг нажатой кнопки. 

IndexError: list index out of range

Лист. 6. Ошибка.

На листинге 6 представлена ошибка, возникающая в функции play в том случае, когда игрок нажимает последние кнопки из нижнего ряда.  Но проблема не только в этом.

from tkinter import *               # графическая библиотека
from random import shuffle          # перемешать список suffle(A)
column = 7                          # столбцы
row = 7                             # строки
btn = []
playground = [1,0,0,0,0] * (row*column//5+1)      # виртуальное игровое поле
shuffle(playground)

def play(n):                        # функция обработчик нажатия на кнопку
    m = (playground[n+1] + playground[n-1] +
         playground[n+column] + playground[n-column] +
         playground[n+column+1] + playground[n+column-1] +
         playground[n-column+1] + playground[n-column-1])
    if playground[n] == 1:
        btn[n].config(text='M')
        btn[n].config(bg='#f88', activebackground='#f66')
    elif m == 0:
        btn[n].config(bg='#afa', activebackground='#7f7')
    else :
        btn[n].config(text=m)

for i in range(row):
    f = Frame()                     # Фрейм
    f.pack(expand=YES, fill=BOTH)   # Вывод фрейма на экран
    for j in range(column):
        n = i * column + j
        btn += [Button(f)]
        btn[n].pack(expand=YES, fill=BOTH, side=LEFT)
        btn[n].config(width=3, height=2)
        btn[n].config(bg='#ccc', activebackground='#aaa')
        btn[n].config(command=lambda n=n: play(n))

mainloop()                          # главный цикл программы

Лист. 7. Увеличен размер игрового поля. Программа теперь не выводит значения из списка playground на игровое поле.

Рис. 1.

from tkinter import *               # графическая библиотека
from random import shuffle          # перемешать список suffle(A)
column = 7                          # столбцы
row = 7                             # строки
btn = []
playground = [1,0,0,0,0] * (row*column//5+1)      # виртуальное игровое поле
shuffle(playground)

def play(n):                        # функция обработчик нажатия на кнопку
    m = 0
    for i in [n-column, n, n+column]:
        for j in [-1, 0, 1]:
            m += playground[i+j]
    if playground[n] == 1:
        btn[n].config(text='M')
        btn[n].config(bg='#f88', activebackground='#f66')
    elif m == 0:
        btn[n].config(bg='#afa', activebackground='#7f7')
    else :
        btn[n].config(text=m)

for i in range(row):
    f = Frame()                     # Фрейм
    f.pack(expand=YES, fill=BOTH)   # Вывод фрейма на экран
    for j in range(column):
        n = i * column + j
        btn += [Button(f)]
        btn[n].pack(expand=YES, fill=BOTH, side=LEFT)
        btn[n].config(width=3, height=2)
        btn[n].config(bg='#ccc', activebackground='#aaa')
        btn[n].config(command=lambda n=n: play(n))

mainloop()                          # главный цикл программы

Лист. 8. Подсчёт количества мин осуществляется теперь в цикле. 

from tkinter import *               # графическая библиотека
from random import shuffle          # перемешать список suffle(A)
column = 7                          # столбцы
row = 7                             # строки
btn = []
playground = [1,0,0,0,0] * (row*column//5+1)      # виртуальное игровое поле
shuffle(playground)

def play(n):                        # функция обработчик нажатия на кнопку
    m = 0
    for i in [n-column, n, n+column]:
        for j in [-1, 0, 1]:
            if (i+j >= 0 and i+j < row*column
                and i//column == (i+j)//column):
                m += playground[i+j]
    if playground[n] == 1:
        btn[n].config(text='M')
        btn[n].config(bg='#f88', activebackground='#f66')
    elif m == 0:
        btn[n].config(bg='#afa', activebackground='#7f7')
    else :
        btn[n].config(text=m)

for i in range(row):
    f = Frame()                     # Фрейм
    f.pack(expand=YES, fill=BOTH)   # Вывод фрейма на экран
    for j in range(column):
        n = i * column + j
        btn += [Button(f)]
        btn[n].pack(expand=YES, fill=BOTH, side=LEFT)
        btn[n].config(width=3, height=2)
        btn[n].config(bg='#ccc', activebackground='#aaa')
        btn[n].config(command=lambda n=n: play(n))

mainloop()                          # главный цикл программы

Лист. 9. Исправлены краевые ошибки подсчёта мин. 

Рис. 2.

from tkinter import *               # графическая библиотека
from random import shuffle          # перемешать список suffle(A)
column = 7                          # столбцы
row = 7                             # строки
btn = []
playground = [1,0,0,0,0] * (row*column//5+1)      # виртуальное игровое поле
shuffle(playground)

def play(n):                        # функция обработчик нажатия на кнопку
    m = 0
    for i in [n-column, n, n+column]:
        for j in [-1, 0, 1]:
            if (i+j >= 0 and i+j < row*column
                and i//column == (i+j)//column):
                m += playground[i+j]
    if playground[n] == 1:
        btn[n].config(text='M')
        btn[n].config(bg='#f88', activebackground='#f66')
    elif m == 0:
        btn[n].config(bg='#afa', activebackground='#7f7')
    else :
        btn[n].config(text=m)

def marker(n):
    btn[n].config(text='F', bg='#ffa', activebackground='#ff7')

for i in range(row):
    f = Frame()                     # Фрейм
    f.pack(expand=YES, fill=BOTH)   # Вывод фрейма на экран
    for j in range(column):
        n = i * column + j
        btn += [Button(f)]
        btn[n].pack(expand=YES, fill=BOTH, side=LEFT)
        btn[n].config(width=3, height=2)
        btn[n].config(bg='#ccc', activebackground='#aaa')
        btn[n].config(command=lambda n=n: play(n))
        btn[n].bind('<Button-3>', lambda event, n=n: marker(n))

mainloop()                          # главный цикл программы

Лист. 10. Создаём функцию marker.

from tkinter import *               # графическая библиотека
from random import shuffle          # перемешать список suffle(A)
column = 7                          # столбцы
row = 7                             # строки
btn = []
playground = [1,0,0,0,0] * (row*column//5+1)      # виртуальное игровое поле
shuffle(playground)

def play(n):                        # функция обработчик нажатия на кнопку
    m = 0
    for i in [n-column, n, n+column]:
        for j in [-1, 0, 1]:
            if (i+j >= 0 and i+j < row*column
                and i//column == (i+j)//column):
                m += playground[i+j]
    if playground[n] == 1:
        btn[n].config(text='M', bg='#f88', activebackground='#f66')
    elif m == 0:
        btn[n].config(text=0, fg='#afa', bg='#afa', activebackground='#7f7')
    else :
        btn[n].config(text=m, bg='#ccc', activebackground='#aaa')

def marker(n):
    if btn[n].cget('text') == '':
        btn[n].config(text='F', bg='#ffa', activebackground='#ff7')

for i in range(row):
    f = Frame()                     # Фрейм
    f.pack(expand=YES, fill=BOTH)   # Вывод фрейма на экран
    for j in range(column):
        n = i * column + j
        btn += [Button(f)]
        btn[n].pack(expand=YES, fill=BOTH, side=LEFT)
        btn[n].config(width=3, height=2)
        btn[n].config(bg='#ccc', activebackground='#aaa')
        btn[n].config(command=lambda n=n: play(n))
        btn[n].bind('<Button-3>', lambda event, n=n: marker(n))

mainloop()                          # главный цикл программы

Лист. 11. Прорабатываем логику установки маркера (флажка).

#!/usr/bin/env python3
# Mines
# This is my version of the game, known as mines or Minesweeper.
#
# Created on December 21, 2023.
# Author: Diorditsa A.
#
# mines.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 *               # графическая библиотека
from random import shuffle          # перемешать список suffle(A)
column = 7                          # столбцы
row = 7                             # строки
btn = []
playground = [1,0,0,0,0] * (row*column//5+1)      # виртуальное игровое поле
shuffle(playground)

def play(n):                        # функция обработчик нажатия на кнопку
    m = 0
    for i in [n-column, n, n+column]:
        for j in [-1, 0, 1]:
            if (i+j >= 0 and i+j < row*column
                and i//column == (i+j)//column):
                m += playground[i+j]
    if playground[n] == 1:
        btn[n].config(text='M', bg='#f88', activebackground='#f66')
    elif m == 0:
        btn[n].config(text=0, fg='#afa', bg='#afa', activebackground='#7f7')
    else :
        btn[n].config(text=m, bg='#ccc', activebackground='#aaa')

def marker(n):
    if btn[n].cget('text') == '':
        btn[n].config(text='F', bg='#ffa', activebackground='#ff7')
    elif btn[n].cget('text') == 'F':
        btn[n].config(text='', bg='#ccc', activebackground='#aaa')

for i in range(row):
    f = Frame()                     # Фрейм
    f.pack(expand=YES, fill=BOTH)   # Вывод фрейма на экран
    for j in range(column):
        n = i * column + j
        btn += [Button(f)]
        btn[n].pack(expand=YES, fill=BOTH, side=LEFT)
        btn[n].config(width=3, height=2)
        btn[n].config(bg='#ccc', activebackground='#aaa')
        btn[n].config(command=lambda n=n: play(n))
        btn[n].bind('<Button-3>', lambda event, n=n: marker(n))

mainloop()                          # главный цикл программы

Лист. 12. Делаем возможным снятие маркера (флажка).