От простого к сложному, пишем на Python игру-головоломку в стиле Puzzle. По русски - пятнашки. Программируем графический интерфейс (GUI) с применением библиотеки Tkinter.
План работы:
- Создание графического интерфейса.
- Программирование движений.
- Программирование движения по правилам
- Перемешивание кнопок.
Программирование графического интерфейса
from tkinter import *
Button().pack()
Button().pack()
Button().pack()
Button().pack()
mainloop()
Прог. 1. Создание кнопок для игры.
Рис. 1. Создание оконного графического интерфейса (GUI) для игры "Пятнашки".
from tkinter import *
Button().pack(side=LEFT, expand=YES, fill=BOTH)
Button().pack(side=LEFT, expand=YES, fill=BOTH)
Button().pack(side=LEFT, expand=YES, fill=BOTH)
Button().pack(side=LEFT, expand=YES, fill=BOTH)
mainloop()
Прог. 2. Размещение кнопок в один ряд.
Рис. 2. Создание оконного графического интерфейса (GUI) для игры "Пятнашки".
from tkinter import *
frm = Frame()
frm.pack(expand=YES, fill=BOTH)
Button(frm).pack(side=LEFT, expand=YES, fill=BOTH)
Button(frm).pack(side=LEFT, expand=YES, fill=BOTH)
Button(frm).pack(side=LEFT, expand=YES, fill=BOTH)
Button(frm).pack(side=LEFT, expand=YES, fill=BOTH)
mainloop()
Прог. 3. Размещение 4-х кнопок в одном фрейме.
Задание: В программе 3 скопируйте 6 строк, начиная со строки frm... и вставьте эту копию перед строкой mainloop() 3 раза. Запустите программу и посмотрите что у Вас получилось.
from tkinter import *
frm = Frame()
frm.pack(expand=YES, fill=BOTH)
for j in range(0, 4):
Button(frm).pack(side=LEFT, expand=YES, fill=BOTH)
mainloop()
Прог. 4. Размещение 4-х кнопок в одном фрейме с использованием цикла for.
from tkinter import *
for i in range(0, 4):
frm = Frame()
frm.pack(expand=YES, fill=BOTH)
for j in range(0, 4):
Button(frm).pack(side=LEFT, expand=YES, fill=BOTH)
mainloop()
Прог. 5. Размещение 16-ти кнопок в 4-х фреймах с использованием вложенных циклов for.
Рис. 3. Создание оконного графического интерфейса (GUI) для игры "Пятнашки".
from tkinter import *
for i in range(0, 4):
frm = Frame()
frm.pack(expand=YES, fill=BOTH)
for j in range(0, 4):
Button(frm, text=i*4+j).pack(side=LEFT, expand=YES, fill=BOTH)
mainloop()
Прог. 6. Размещение на кнопках их номеров.
Рис. 4. Создание оконного графического интерфейса (GUI) для игры "Пятнашки".
from tkinter import *
for i in range(0, 4):
frm = Frame()
frm.pack(expand=YES, fill=BOTH)
for j in range(0, 4):
Button(frm, text=i*4+j, font=('mono', 20, 'bold'),
width=3, height=2).pack(side=LEFT, expand=YES, fill=BOTH)
mainloop()
Прог. 7. Добавлен стиль кнопок.
Рис. 5. Создан оконный графический интерфейс (GUI) для игры "Пятнашки".
Программирование движений кнопок
from tkinter import *
btn = []
for i in range(0, 4):
frm = Frame()
frm.pack(expand=YES, fill=BOTH)
for j in range(0, 4):
btn += [Button(frm, text=i*4+j, font=('mono', 20, 'bold'),
width=3, height=2)]
btn[i*4+j].pack(side=LEFT, expand=YES, fill=BOTH)
print(*btn, sep='\n')
mainloop()
Прог. 8. Создан список кнопок.
====== RESTART: ~/puzle06.py ======
.!frame.!button
.!frame.!button2
.!frame.!button3
.!frame.!button4
.!frame2.!button
.!frame2.!button2
.!frame2.!button3
.!frame2.!button4
.!frame3.!button
.!frame3.!button2
.!frame3.!button3
.!frame3.!button4
.!frame4.!button
.!frame4.!button2
.!frame4.!button3
.!frame4.!button4
Листинг 1. Список кнопок.
from tkinter import *
btn = []
def play(n):
print(n)
for i in range(0, 4):
frm = Frame()
frm.pack(expand=YES, fill=BOTH)
for j in range(0, 4):
btn += [Button(frm, text=i*4+j, font=('mono', 20, 'bold'),
width=3, height=2, command=lambda n=i*4+j: play(n))]
btn[i*4+j].pack(side=LEFT, expand=YES, fill=BOTH)
mainloop()
Прог. 9. Создана функция play() и функция play() зарегистрирована как обработчик события "нажатие на кнопку".
======== RESTART: ~/puzzle.py ========
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Листинг 2. Номера кнопок.
Если 0 — это пустая клетка, то мы уже имеем нерешаемую игровую ситуацию. Пустая клетка должна быть в конце поля — в правом нижнем углу.
from tkinter import *
btn = []
playArea = list(range(1, 16))
playArea.append(0)
def play(n):
print(n)
for i in range(0, 4):
frm = Frame()
frm.pack(expand=YES, fill=BOTH)
for j in range(0, 4):
btn += [Button(frm, text=playArea[i*4+j], font=('mono', 20, 'bold'),
width=3, height=2,
command=lambda n=i*4+j: play(n))]
btn[i*4+j].pack(side=LEFT, expand=YES, fill=BOTH)
mainloop()
Прог. 10. Создан список playArea.
Рис. 6. Кнопки расположены а правильном для игры "Пятнашки" порядке.
В списке playArea = list(range(1, 16), индекс этого списка — номер кнопки, элемент списка — цифра на кнопке. Последний элемент этого списка мы добавили со значением 0.
Для написания функции play() нам осталось:
- Найти m = playArea.index(0) — номер пустой кнопки.
- Определить условие при котором кнопка играет
- Поменять местами кнопку с цифрой и пустую кнопку playArea[m] = playArea[n]; playArea[n] = 0 Или playArea[m], playArea[n] = playArea[n], playArea[m]
- Изменить свойство text у тех кнопок которые сыграли
from tkinter import *
btn = []
playArea = list(range(1, 16))
playArea.append(0)
def play(n):
m = playArea.index(0)
playArea[m], playArea[n] = playArea[n], playArea[m]
btn[m].config(text=playArea[m])
btn[n].config(text=" ")
for i in range(0, 4):
frm = Frame()
frm.pack(expand=YES, fill=BOTH)
for j in range(0, 4):
btn += [Button(frm, text=playArea[i*4+j], font=('mono', 20, 'bold'),
width=3, height=2,
command=lambda n=i*4+j: play(n))]
btn[i*4+j].pack(side=LEFT, expand=YES, fill=BOTH)
btn[15].config(text=" ")
mainloop()
Прог. 11. Создана функция play(), без учёта правил игры и удалён символ "0" с соответствующей кнопки.
Рис. 7. На кнопке №16 цифра не отображается.
from tkinter import *
btn = []
playArea = list(range(1, 16))
playArea.append(0)
def play(n):
m = playArea.index(0)
if abs(m - n) == 1 and n//4 == m//4 or abs(m - n) == 4:
playArea[m], playArea[n] = playArea[n], playArea[m]
btn[m].config(text=playArea[m])
btn[n].config(text=" ")
for i in range(0, 4):
frm = Frame()
frm.pack(expand=YES, fill=BOTH)
for j in range(0, 4):
btn += [Button(frm, text=playArea[i*4+j], font=('mono', 20, 'bold'),
width=3, height=2,
command=lambda n=i*4+j: play(n))]
btn[i*4+j].pack(side=LEFT, expand=YES, fill=BOTH)
btn[15].config(text=" ")
mainloop()
Прог. 12. Учтены правила игры.
Неплохо бы, в начале игры случайным образом понажимать кнопочки несколько тысяч раз.
#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
#
# Puzzle.py
# Copyright (C) 2021 Aleksandr Diorditsa, see <https://adior.ru>
# I thank Sergey Polozkov for checking the code for hidden errors.
# I want to thank all my students for the inspiration they give me.
#
# brownian-motion.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Puzzle.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
from tkinter import *
from random import choice
btn = []
playArea = list(range(1, 16))
playArea.append(0)
def play(n):
m = playArea.index(0)
if abs(m - n) == 1 and n//4 == m//4 or abs(m - n) == 4:
playArea[m], playArea[n] = playArea[n], playArea[m]
btn[m].config(text=playArea[m])
btn[n].config(text=" ")
for i in range(0, 4):
frm = Frame()
frm.pack(expand=YES, fill=BOTH)
for j in range(0, 4):
btn += [Button(frm, text=playArea[i*4+j], font=('mono', 20, 'bold'),
width=3, height=2,
command=lambda n=i*4+j: play(n))]
btn[i*4+j].pack(side=LEFT, expand=YES, fill=BOTH)
for i in range(0, 3000):
play(choice(range(0, 16)))
mainloop()
Прог. 14. Кнопочки перемешаны в случайном порядке.
Рис. 8. Игра "Пятнашки", кнопки перемешаны.
И ещё один пример программы Puzzle, с использованием класса. Этот вариант игры Puzzle позволяет выбрать размер поля с помощью константы SIZE.
#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
#
# puzzle.py
# Copyright (C) 2021 Aleksandr Diorditsa <a-dior ? yandex.ru>
# I thank Sergey Polozkov for checking the code for hidden errors.
#
# OS.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# puzzle.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
from tkinter import *
from random import choice
SIZE = 7
frm = []; btn = []
playArea = list(range(1, SIZE*SIZE)) + [0]
class puzzle(Button):
def __init__(self, number=0, parent=None, **config):
self.number = number
Button.__init__(self, parent, **config)
self.pack(side=LEFT, expand=YES, fill=BOTH)
self.config(font=('mono', 20, 'bold'), width=3, height=2, command=self.play)
def play(self):
m = playArea.index(0)
if (abs(m - self.number) + abs(self.number//SIZE - m//SIZE)) == 1 or abs(m - self.number) == SIZE:
playArea[m], playArea[self.number] = playArea[self.number], playArea[m]
btn[m].config(text=playArea[m])
self.config(text=" ")
for i in range(0, SIZE):
frm.append(Frame())
frm[i].pack(expand=YES, fill=BOTH)
for j in range(0, SIZE):
btn.append(puzzle((i*SIZE+j), frm[i]))
for i in range(0, SIZE**6):
btn[choice(range(0, SIZE*SIZE))].play()
mainloop()
Оживить игру можно анимацией созданной с помощью функции after() и для азарта добавим счётчик времени из библиотеки time.
#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
#
# puzzle.py
# Copyright (C) 2021 Aleksandr Diorditsa <a-dior ? yandex.ru>
# I thank Sergey Polozkov for checking the code for hidden errors.
#
# OS.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# puzzle.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
from tkinter import *
from random import choice
from time import time
SIZE = 4
frm = []; btn = []
playArea = list(range(1, SIZE*SIZE)) + [0]
game = False
timeGame = time()
class puzzle(Button):
def __init__(self, number=0, parent=None, **config):
self.number = number
Button.__init__(self, parent, **config)
self.pack(side=LEFT, expand=YES, fill=BOTH)
self.config(font=('mono', 20, 'bold'), width=3, height=2, command=self.play)
def play(self):
m = playArea.index(0)
if (abs(m - self.number) + abs(self.number//SIZE - m//SIZE)) == 1 or abs(m - self.number) == SIZE:
playArea[m], playArea[self.number] = playArea[self.number], playArea[m]
btn[m].config(text=playArea[m])
self.config(text=" ")
global game
if game:
for i in range(0, SIZE*SIZE):
if playArea[i] != i+1:
break
if i+2 == SIZE*SIZE:
tk.title("You Win! "+str(int(timeGame-time()))+"сек.")
game = False
global sort
sort = 0
tk.after(5000, newGame)
tk = Tk()
for i in range(0, SIZE):
frm.append(Frame())
frm[i].pack(expand=YES, fill=BOTH)
for j in range(0, SIZE):
btn.append(puzzle((i*SIZE+j), frm[i]))
sort = 0
def newGame():
global sort
sort += 1
if sort < SIZE**5:
m = playArea.index(0)
n = choice(range(0, SIZE*SIZE))
if (abs(m - n) + abs(n//SIZE - m//SIZE)) == 1 or abs(m - n) == SIZE:
btn[n].play()
tk.after(1, newGame)
else:
newGame()
else:
tk.title("Puzzle " + str(SIZE))
global game
game = True
timeGame = time()
newGame()