Написать игру Сапёр (Mines) на Python с использованием библиотеки Tkinter оказалось не сложно. В этой статье покажем все этапы создания этой игры, которая массово была представлена публике в Windows 95. Следует отметить, что в Windows 3.11 игра winmine уже была. Но кто теперь это вспомнит?
Создадим поле из кнопок 16х16=256 кнопок.
from tkinter import *
from random import choice
frm = []; btn = [] # Списки с фреймами и кнопками
tk = Tk()
tk.title('Achtung, Minen!')
for i in range(0, 16):
frm.append(Frame())
frm[i].pack(expand=YES, fill=BOTH)
for j in range(0, 16):
btn.append(Button(frm[i], text=' ',
font=('mono', 16, 'bold'),
width=2, height=1,))
btn[i*16+j].pack(side=LEFT, expand=NO, fill=Y)
mainloop()
Кнопки размещаются в 16 горизонтальных фреймах. Список btn состоит из 256 кнопок.
Создадим список playArea - игровое поле и переменную счётчик ходов — nMoves. Игровое поле playArea содержит 256 элементов (целые числа). Где число -1 мина, числа 0 ... 8 количество мин вокруг клетки. Будем ставить мины на поле после первого хода игрока, с тем, чтобы первый ход не закончился взрывом.
Каждый ход игрока выводит на нажатую кнопку соответствующее значение из списка playArea и деактивирует кнопку.
from tkinter import *
from random import choice
frm = []; btn = [] # Списки с фреймами и кнопками
playArea = []; nMoves = 0 # Игровое поле и счётчик ходов
def play(n): # n - номер нажатой кнопки
global nMoves
nMoves += 1
if nMoves == 1: # Если это первый ход игрока,
i = 0
while i<40: # поставим мины,
j = choice(range(0, 256))
if j != n and playArea[j] != -1:
playArea[j] = -1
i += 1
for i in range(0, 256): # подсчитаем количесво мин вокруг каждой клетки
if playArea[i] != -1:
k = 0
if i not in range(0, 256, 16):
if playArea[i-1] == -1: k += 1 # слева
if i > 15:
if playArea[i-17] == -1: k += 1 # слева сверху
if i < 240:
if playArea[i+15] == -1: k += 1 # слева снизу
if i not in range(-1, 256, 16):
if playArea[i+1] == -1: k += 1 # справа
if i > 15:
if playArea[i-15] == -1: k += 1 # справа сверху
if i < 240:
if playArea[i+17] == -1: k += 1 # справа снизу
if i > 15:
if playArea[i-16] == -1: k += 1 # сверху
if i < 240:
if playArea[i+16] == -1: k += 1 # снизу
playArea[i] = k
btn[n].config(text=playArea[n], state=DISABLED) # Отображаем игровую ситуацию
tk = Tk()
tk.title('Achtung, Minen!')
for i in range(0, 16): # Размещаем кнопки
frm.append(Frame())
frm[i].pack(expand=YES, fill=BOTH)
for j in range(0, 16):
btn.append(Button(frm[i], text=' ',
font=('mono', 16, 'bold'),
width=2, height=1,
command=lambda n=i*16+j: play(n)))
btn[i*16+j].pack(side=LEFT, expand=NO, fill=Y)
playArea.append(0) # Создаём элементы списка playArea
mainloop()
С такой программой уже можно играть в сапёра, но нет, ставших нам уже привычными, некоторых мелочей:
- Вместо "0" должна отображаться пустая кнопка с изменённым цветом.
- Вместо "-1" необходимо вывести изображение мины.
- Если мы нарвались на мину:
- необходимо вывести на экран все мины и
- необходимо вывести на экран "Game Over"
- в дальнейшем не допустить вывод на экран сообщения "You win!"
- Если игрок открыл все поля не занятые минами, вывести на экран сообщение "You win!"
- Необходимо добавить возможность с помощью правой кнопки мыши помечать клетку под которой возможно скрывается мина.
from tkinter import *
from random import choice
frm = []; btn = [] # Списки с фреймами и кнопками
playArea = []; nMoves = 0 # Игровое поле и счётчик ходов
def play(n): # n - номер нажатой кнопки
global nMoves
nMoves += 1
if nMoves == 1: # Если это первый ход игрока,
i = 0
while i<40: # поставим мины,
j = choice(range(0, 256))
if j != n and playArea[j] != -1:
playArea[j] = -1
i += 1
for i in range(0, 256): # подсчитаем количесво мин вокруг каждой клетки
if playArea[i] != -1:
k = 0
if i not in range(0, 256, 16):
if playArea[i-1] == -1: k += 1 # слева
if i > 15:
if playArea[i-17] == -1: k += 1 # слева сверху
if i < 240:
if playArea[i+15] == -1: k += 1 # слева снизу
if i not in range(-1, 256, 16):
if playArea[i+1] == -1: k += 1 # справа
if i > 15:
if playArea[i-15] == -1: k += 1 # справа сверху
if i < 240:
if playArea[i+17] == -1: k += 1 # справа снизу
if i > 15:
if playArea[i-16] == -1: k += 1 # сверху
if i < 240:
if playArea[i+16] == -1: k += 1 # снизу
playArea[i] = k
btn[n].config(text=playArea[n], state=DISABLED) # Отображаем игровую ситуацию
if playArea[n] == 0:
btn[n].config(text=' ', bg='#ccb')
elif playArea[n] == -1:
if nMoves < (256 - 40): # Если игрок ещё не выиграл, то проиграл
tk.title('Your game is over.')
nMoves = 256 # Если проиграл, то уже не выиграет
for i in range(0, 256):
if playArea[i] == -1:
btn[i].config(text='\u2665')
if nMoves == (256 - 40): # Если все клетки открыты, это победа
tk.title('You win!')
def marker(n): # помечаем клетку под которой возможно скрывается мина.
btn[n].config(text='\u2661')
tk = Tk()
tk.title('Achtung, Minen!')
for i in range(0, 16): # Размещаем кнопки
frm.append(Frame())
frm[i].pack(expand=YES, fill=BOTH)
for j in range(0, 16):
btn.append(Button(frm[i], text=' ',
font=('mono', 16, 'bold'),
width=2, height=1,
command=lambda n=i*16+j: play(n)))
btn[i*16+j].pack(side=LEFT, expand=NO, fill=Y)
btn[i*16+j].bind('<Button-3>', lambda event, n=i*16+j: marker(n))
playArea.append(0) # Создаём элементы списка playArea
mainloop()
Добавим кнопку "New game" и взрывы мин в случае поражения.
# Mines
# This is my version of the game, known as mines or Minesweeper.
#
# Created on June 25, 2021.
# Author: Diorditsa A.
# I thank Sergey Polozkov for checking the code for hidden errors.
#
# 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 choice
frm = []; btn = [] # Списки с фреймами и кнопками
playArea = []; nMoves = 0; mrk=40 # Игровое поле, счётчик ходов и маркеров
def play(n): # n - номер нажатой кнопки
global nMoves, mrk
nMoves += 1
if nMoves == 1: # Если это первый ход игрока,
tk.title('Achtung, '+str(mrk)+' Minen!')
i = 0
while i<40: # поставим мины,
j = choice(range(0, 256))
if j != n and playArea[j] != -1:
playArea[j] = -1
i += 1
for i in range(0, 256): # подсчитаем количесво мин вокруг каждой клетки
if playArea[i] != -1:
k = 0
if i not in range(0, 256, 16):
if playArea[i-1] == -1: k += 1 # слева
if i > 15:
if playArea[i-17] == -1: k += 1 # слева сверху
if i < 240:
if playArea[i+15] == -1: k += 1 # слева снизу
if i not in range(-1, 256, 16):
if playArea[i+1] == -1: k += 1 # справа
if i > 15:
if playArea[i-15] == -1: k += 1 # справа сверху
if i < 240:
if playArea[i+17] == -1: k += 1 # справа снизу
if i > 15:
if playArea[i-16] == -1: k += 1 # сверху
if i < 240:
if playArea[i+16] == -1: k += 1 # снизу
playArea[i] = k
btn[n].config(text=playArea[n], state=DISABLED) # Отображаем игровую ситуацию
if playArea[n] == 0:
btn[n].config(text=' ', bg='#ccb')
elif playArea[n] == -1:
btn[n].config(text='\u2665')
if nMoves <= (256 - 40): # Если игрок ещё не выиграл, то проиграл
tk.title('Your game is over.')
nMoves = 256 # Если проиграл, то уже не выиграет
chainReaction(0) # Цепная реакция
if nMoves == (256 - 40): # Если все клетки открыты, это победа
tk.title('You win!')
winner(0)
def chainReaction(j): # Цепная реакция
for i in range(j, 256):
if playArea[i] == -1 and btn[i].cget('text') == ' ':
btn[i].config(text='\u2665')
btn[i].flash()
tk.bell()
tk.after(50, chainReaction, i + 1)
break
def winner(j):
for i in range(j, 256):
if playArea[i] == 0:
btn[i].config(state=NORMAL, text='☺')
btn[i].flash()
tk.bell()
btn[i].config(text=' ', state=DISABLED)
tk.after(50, winner, i + 1)
break
def marker(n): # помечаем клетку под которой возможно скрывается мина.
global mrk
if (btn[n].cget('state')) != 'disabled':
if btn[n].cget('text') == '\u2661':
btn[n].config(text=' ')
mrk += 1
else:
btn[n].config(text='\u2661')
mrk -= 1
tk.title('Achtung, '+str(mrk)+' Minen!')
def newGame(): # Чистим переменную nMoves, mrk и список playArea и кнопки
global nMoves, btnBG, mrk
nMoves = 0; mrk = 40
for i in range(0, 256):
playArea[i] = 0
btn[i].config(text=' ', state=NORMAL, bg=btnBG)
tk = Tk()
for i in range(0, 16): # Размещаем кнопки
frm.append(Frame())
frm[i].pack(expand=YES, fill=BOTH)
for j in range(0, 16):
btn.append(Button(frm[i], text=' ',
font=('mono', 16, 'bold'),
width=2, height=1,
command=lambda n=i*16+j: play(n)))
btn[i*16+j].pack(side=LEFT, expand=NO, fill=Y)
btn[i*16+j].bind('<Button-3>', lambda event, n=i*16+j: marker(n))
playArea.append(0) # Создаём элементы списка playArea
tk.update()
Button(tk, text='New game', font=(16), # Создаём кнопку "New game"
command=newGame).pack(side=LEFT, expand=YES, fill=Y)
btnBG = btn[0].cget('bg') # Запоминаем цвет кнопки по умолчанию
mainloop()
Рис. Игра Сапёр.
Очередная версия:
# Mines
# This is my version of the game, known as mines or Minesweeper.
#
# Created on June 25, 2021.
# Author: Diorditsa A.
# I thank Sergey Polozkov for checking the code for hidden errors?
#
# 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 choice
import time
frm = []; btn = [] # Списки с фреймами и кнопками
xBtn = 16; yBtn = 16 # Размеры поля (количество кнопок)
playTime = 0 # Время игры
mines = xBtn * yBtn * 10 // 64 # Количество мин
imgMark = '\u2661'; imgMine = '\u2665' # Символ маркера и мины
playArea = []; nMoves = 0; mrk=0 # Игровое поле, счётчик ходов и маркеров
tk = Tk()
tk.title('Achtung, Minen!')
tk.geometry(str(44*xBtn)+'x'+str(44*yBtn+10))
def play(n): # n - номер нажатой кнопки
global xBtn, yBtn, mines, nMoves, mrk, playTime
if len(playArea) < xBtn*yBtn: # Если поле ещё не создано
return()
nMoves += 1
if nMoves == 1: # Если это первый ход игрока,
playTime = time.time()
i = 0
while i<mines: # поставим мины,
j = choice(range(0, xBtn*yBtn))
if j != n and playArea[j] != -1:
playArea[j] = -1
i += 1
for i in range(0, xBtn*yBtn): # подсчитаем количесво мин вокруг каждой клетки
if playArea[i] != -1:
k = 0
if i not in range(0, xBtn*yBtn, xBtn):
if playArea[i-1] == -1: k += 1 # слева
if i > xBtn-1:
if playArea[i-xBtn-1] == -1: k += 1 # слева сверху
if i < xBtn*yBtn-xBtn:
if playArea[i+xBtn-1] == -1: k += 1 # слева снизу
if i not in range(-1, xBtn*yBtn, xBtn):
if playArea[i+1] == -1: k += 1 # справа
if i > xBtn-1:
if playArea[i-xBtn+1] == -1: k += 1 # справа сверху
if i < xBtn*yBtn-xBtn:
if playArea[i+xBtn+1] == -1: k += 1 # справа снизу
if i > xBtn-1:
if playArea[i-xBtn] == -1: k += 1 # сверху
if i < xBtn*yBtn-xBtn:
if playArea[i+xBtn] == -1: k += 1 # снизу
playArea[i] = k
if btn[n].cget('text') == imgMark: # Если поле было промаркировано
mrk -= 1
tk.title('Achtung, '+str(mines-mrk)+' Minen!')
btn[n].config(text=playArea[n], state=DISABLED, bg='white') # Отображаем игровую ситуацию
if playArea[n] == 0: # Пустое поле без соседей
btn[n].config(text=' ', bg='#ccb')
elif playArea[n] == -1: # Ой мина!
btn[n].config(text=imgMine)
if nMoves <= (xBtn*yBtn - mines) or mines >= mrk: # Если игрок ещё не выиграл, то проиграл
nMoves = 32000 # Если проиграл, то уже не выиграет
chainReaction(0) # Цепная реакция
tk.title('Your game is over.')
if nMoves == (xBtn*yBtn - mines) and mines == mrk: # Если все клетки открыты и мины помечены
tk.title('You win! '+str(int(time.time() - playTime))+' сек')
winner(0)
def chainReaction(j): # Цепная реакция
if j <= len(playArea): # Если не запустили новую игру
for i in range(j, xBtn*yBtn):
if playArea[i] == -1 and btn[i].cget('text') == ' ':
btn[i].config(text=imgMine)
btn[i].flash()
tk.bell()
tk.after(50, chainReaction, i + 1)
break
def winner(j): # Победа
if j <= len(playArea): # Если не запустили новую игру
for i in range(j, xBtn*yBtn):
if playArea[i] == 0:
btn[i].config(state=NORMAL, text='☺')
btn[i].flash()
tk.bell()
btn[i].config(text=' ', state=DISABLED)
tk.after(50, winner, i + 1)
break
def marker(n): # помечаем то, где возможно скрывается мина.
global mrk, mines, playTime
if (btn[n].cget('state')) != 'disabled':
if btn[n].cget('text') == imgMark:
btn[n].config(text=' ')
mrk -= 1
else:
btn[n].config(text=imgMark, fg='blue')
mrk += 1
tk.title('Achtung, '+str(mines-mrk)+' Minen!')
if nMoves == (xBtn*yBtn - mines) and mines == mrk: # Если все клетки открыты и мины помечены
tk.title('You win! '+str(int(time.time() - playTime))+' сек')
winner(0)
def newGame():
global xBtn, yBtn, mines, nMoves, mrk
mines = xBtn * yBtn * 10 // 64
nMoves = 0; mrk=0
playArea.clear()
if len(btn) != 0:
for i in range (0, len(btn)):
btn[i].destroy()
btn.clear()
for i in range (0, len(frm)):
frm[i].destroy()
frm.clear()
playground()
tk.title('Achtung, '+str(mines-mrk)+' Minen!')
def set5x5():
global xBtn, yBtn
xBtn = 5; yBtn = 5
newGame()
def set8x8():
global xBtn, yBtn
xBtn = 8; yBtn = 8
newGame()
def set10x14():
global xBtn, yBtn
xBtn = 10; yBtn = 14
newGame()
def set16x16():
global xBtn, yBtn
xBtn = 16; yBtn = 16
newGame()
def set32x32():
global xBtn, yBtn
xBtn = 32; yBtn = 32
newGame()
def playground():
global xBtn, yBtn
for i in range(0, yBtn):
frm.append(Frame())
frm[i].pack(expand=YES, fill=BOTH)
for j in range(0, xBtn):
btn.append(Button(frm[i], text=' ',font=('mono', 16, 'bold'),
width=1, height=1, padx=0, pady=0))
for i in range(0, xBtn*yBtn):
if xBtn*yBtn > 256:
btn[i].config(font=('mono', 8, 'normal'))
btn[i].config(command=lambda n=i: play(n))
btn[i].bind('<Button-3>', lambda event, n=i: marker(n))
btn[i].pack(side=LEFT, expand=YES, fill=BOTH, padx=0, pady=0)
btn[i].update()
playArea.append(0) # Создаём элементы списка playArea
frmTop = Frame() # Создаём кнопки "New game"
frmTop.pack(expand=YES, fill=BOTH)
Label(frmTop, text=' Новая игра: ').pack(side=LEFT, expand=NO, fill=X, anchor=N)
Button(frmTop, text='5x5', font=(16),
command=set5x5).pack(side=LEFT, expand=YES, fill=X, anchor=N)
Button(frmTop, text='8x8', font=(16),
command=set8x8).pack(side=LEFT, expand=YES, fill=X, anchor=N)
Button(frmTop, text='10x14', font=(16),
command=set10x14).pack(side=LEFT, expand=YES, fill=X, anchor=N)
Button(frmTop, text='16x16', font=(16),
command=set16x16).pack(side=LEFT, expand=YES, fill=X, anchor=N)
Button(frmTop, text='32x32', font=(16),
command=set32x32).pack(side=LEFT, expand=YES, fill=X, anchor=N)
mainloop()
Рис. Игра Сапёр с полем 32х32