Пег солитер (peg solitare) — это настольная игра-головоломка для одного игрока, в которой переставляются шарики на доске. Эта игра известна с 1697 года.
С особой любовью к этой игре относился французский король Людовик XIV.
В США игра имеет название Peg Solitaire (колышковый пасьянс). В Великобритании — Solitaire . В Индии — Brainvita. В СССР — Йога.
Мы публикуем методическую разработку для занятий с детьми старше 12-ти лет в кружке программирование на Python.
В игре используется доска с клетками для размещения шариков. Существует английская и европейская доска. Мы выбираем английскую доску:
Рис. 1. Английская доска с шариками для игры в Peg.
Для написания программы игры Peg нам понадобятся изображения шариков в png формате:
Рис. 2. Изображения шариков в png формате.
Правила игры
Разрешён ход только по вертикали или по горизонтали и только с прыжком через соседний шарик на пустое место. После хода, шарик через который перепрыгнули снимается с доски.
Игра заканчивается, если у Вас не осталось разрешённых ходов.
В классическом «Солитере Пег» игрок побеждает, когда на поле остаётся только одна фишка. Победа считается виртуозной, если последняя фишка оказывается в центре. Настоящие профи способны продумать ходы так, что в конце игры в центр попадает тот шарик, которым была начата игра.
Цель игры — освободить всю доску от шариков, оставив последний шарик в центре доски.
Игровое поле представляет из себя квадрат, поделённый на клетки. Семь клеток по горизонтали на семь клеток по вертикали.
from tkinter import *
colores = ['#aa9', '#776', '#ccd'] # Цвета поля
SIDE=7; SIZE=60; R=25 # Размер поля в клетках, клетки в пикселях, радиус шарика
cnv = Canvas(width=SIDE*SIZE, height=SIDE*SIZE) # Объект класса Canvas с именем cnv
cnv.pack()
for y in range(SIDE): # Отрисовка поля
for x in range(SIDE):
cnv.create_rectangle(x*SIZE, y*SIZE, (x+1)*SIZE, (y+1)*SIZE,
fill=colores[0], outline=colores[2], width=1)
mainloop()
Программа 1. Создание игрового поля.
В программе 1 для создания игрового поля мы используем виджет Canvas из библиотеки tkinter. Цвет фона клеток, цвет клеток не доступных в игре и цвет линий разделяющих клетки, определяем в списке colores. Константы SIDE и SIZE определяют количество клеток на стороне поля и размер каждой клетки. Константа R имеет значение равное радиусу шарика.
В двух циклах for методом create_rectangle() класса Canvas мы рисуем игровое поле. Цикл for с переменной цикла x вложен в цикл for с переменной y. Параметры x и y являются порядковыми номерами создаваемого квадрата по горизонтали и вертикали, из которых легко вычислить физические координаты квадрата в пикселях.
Параметрами метода create_rectangle являются координаты верхнего левого угла и правого нижнего угла квадрата (4 параметра). Так же, цвет квадрата, цвет границы и толщина границы квадрата (3) параметра.
Рис. 3. Игровое поле.
from tkinter import *
colores = ['#aa9', '#776', '#ccd'] # Цвета поля
SIDE=7; SIZE=60; R=25 # Размер поля в клетках, клетки в пикселях, радиус шарика
playArea = [0]*SIDE*SIDE # Игровое поле
for i in [0, 1, 5, 6, 7, 8, 12, 13, 35, 36, 40, 41, 42, 43, 47, 48]: playArea[i]=-1
cnv = Canvas(width=SIDE*SIZE, height=SIDE*SIZE) # Объект класса Canvas с именем cnv
cnv.pack()
for y in range(SIDE): # Отрисовка поля
for x in range(SIDE):
cnv.create_rectangle(x*SIZE, y*SIZE, (x+1)*SIZE, (y+1)*SIZE,
fill=colores[0 if playArea[x+y*SIDE] == 0 else 1], outline=colores[2], width=1)
mainloop()
Программа 2. Создание игрового поля.
В программу 2 мы добавили список playArea. Список playArea — это виртуальное игровое поле, каждый элемент этого списка содержит информацию о состоянии одной клетки на игровом поле (доске для игры peg). Количество элементов в списке playArea соответствует колличеству клеток на доске для игры peg.
Если элемент списка playArea имеет значение:
-1, клетка, соответствующая этому элемету находится за пределами игрового поля и ставить на неё шарики запрещено.
0 — клетка не занята.
>0 — номер (ID) установленного на клетку шарика.
В программе 2, в свойстве fill при создании объкта класса cnv.create_rectangle происходит выбор цвета клетки игрового поля в зависимости от значения, записанного в список playArea. Цвет выбирается из списка colores.
Рис. 4. На игровом поле выделены клетки, которые не участвуют в игре.
from tkinter import *
from random import *
file = ['red.png', 'yellow.png', 'gold.png', 'green.png', 'emerald.png', 'cyan.png', 'blue.png', 'pink.png',
'azure.png', 'bronze.png', 'purple.png', 'scarlet.png', 'steel.png', 'silver.png']
colores = ['#aa9', '#776', '#ccd'] # Цвета поля
SIDE=7; SIZE=60; R=25 # Размер поля в клетках, клетки в пикселях, радиус шарика
playArea = [0]*SIDE*SIDE # Игровое поле
for i in [0, 1, 5, 6, 7, 8, 12, 13, 35, 36, 40, 41, 42, 43, 47, 48]: playArea[i]=-1
def newGame(A=[10, 16, 17, 18, 24, 31]):
global playArea, file, img
img = [PhotoImage(file=i) for i in file]
playArea = list(map(lambda x: x if x<0 else 0, playArea)) # Очистка поля
cnv.delete('allBalls') # Удаление шариков
for i in A: # Размещение шариков
x = (i % SIDE) * SIZE + SIZE / 2
y = (i // SIDE) * SIZE + SIZE / 2
playArea[i] = cnv.create_image(x, y, image=choice(img), anchor=CENTER, tag='allBalls')
cnv = Canvas(width=SIDE*SIZE, height=SIDE*SIZE) # Объект класса Canvas с именем cnv
cnv.pack()
for y in range(SIDE): # Отрисовка поля
for x in range(SIDE):
cnv.create_rectangle(x*SIZE, y*SIZE, (x+1)*SIZE, (y+1)*SIZE,
fill=colores[0 if playArea[x+y*SIDE] == 0 else 1], outline=colores[2], width=1)
newGame()
mainloop()
Программа 3. Размещение фигур на игровом поле.
В программу 3 мы добавили функцию newGame. Параметр, передаваемый в функцию newGame() - список, содержащий номера клеток занятых шариками.
В функции newGame(), для очистки списка playArea мы использовали функцию map().
Функция map() используется для применения функции к каждому элементу итерируемого объекта (например, списка или словаря). Функция map() возвращает новый итератор - объект map. Результат преобразования элементов исходного списка содержится в объекте map. В функцию map() передают два аргумента, функцию и итерируемый объект (последовательность).
Функцию очищающую список playArea мы написали с помощью lambda функции. Наша lambda функция в функции map() возвращает 0 для тех элементов списка playArea, которые содержали номера шариков (номера объектов canvas), то есть, обнуляет элементы содержащие значения больше 0.
Объект map мы опять превращаем в список с помощью функции list().
Рис. 5. На игровом поле расставлены шарики.
from tkinter import *
from random import *
file = ['red.png', 'yellow.png', 'gold.png', 'green.png', 'emerald.png', 'cyan.png', 'blue.png', 'pink.png',
'azure.png', 'bronze.png', 'purple.png', 'scarlet.png', 'steel.png', 'silver.png']
colores = ['#aa9', '#776', '#ccd'] # Цвета поля
SIDE=7; SIZE=60; R=25 # Размер поля в клетках, клетки в пикселях, радиус шарика
playArea = [0]*SIDE*SIDE # Игровое поле
for i in [0, 1, 5, 6, 7, 8, 12, 13, 35, 36, 40, 41, 42, 43, 47, 48]: playArea[i]=-1
def ifplay(event): # Разрешение движения
global num, dx, dy
num = event.x // SIZE + event.y // SIZE * SIDE # Исходная клетка
if playArea[num] > 0: # Если клетка с шариком
dx = int(event.x - cnv.coords(playArea[num])[0]) # Смещение курсора
dy = int(event.y - cnv.coords(playArea[num])[1]) # относительно шарика
cnv.tkraise(playArea[num]) # Поднять шарик на верхний слой
cnv.bind("<B1-Motion>", play) # Начать движение
cnv.bind("<B1-ButtonRelease>", moveEnd) # Конец движения
def play(event): # Начало движения
x = int(event.x-cnv.coords(playArea[num])[0]-dx)
y = int(event.y-cnv.coords(playArea[num])[1]-dy)
cnv.move(playArea[num], x, y)
def moveEnd(event): # Конец движения
cnv.unbind("<B1-Motion>")
cnv.unbind("<B1-ButtonRelease>")
def newGame(A=[10, 16, 17, 18, 24, 31]):
global playArea, file, img
img = [PhotoImage(file=i) for i in file]
playArea = list(map(lambda x: x if x<0 else 0, playArea)) # Очистка поля
cnv.delete('allBalls') # Удаление шариков
for i in A: # Размещение шариков
x = (i % SIDE) * SIZE + SIZE / 2
y = (i // SIDE) * SIZE + SIZE / 2
playArea[i] = cnv.create_image(x, y, image=choice(img), anchor=CENTER, tag='allBalls')
cnv = Canvas(width=SIDE*SIZE, height=SIDE*SIZE) # Объект класса Canvas с именем cnv
cnv.bind('<Button-1>', ifplay)
cnv.pack()
for y in range(SIDE): # Отрисовка поля
for x in range(SIDE):
cnv.create_rectangle(x*SIZE, y*SIZE, (x+1)*SIZE, (y+1)*SIZE,
fill=colores[0 if playArea[x+y*SIDE] == 0 else 1], outline=colores[2], width=1)
newGame()
mainloop()
Программа 4. Реализация перемещения шариков по игровому полю.
В программу 4 мы добавили функции ifplay(), play(), moveEnd(). Функцию ifplay() мы зарегистрировали в качестве обработчика события <Button-1> объекта cnv класса Canvas. В функции ifplay() происходит проверка наличия шарика в клетке над которой прижали левую кнопку мыши. В случае, если шарик в районе указателя мыши имеется, регистрируется функция play() в качестве обработчика события <B1-Motion> объекта cnv и функция moveEnd() в качестве обработчика события <B1-ButtonRelease> того же объекта cnv.
В функции play() происходит движение шарика, а в функции moveEnd() движение прекращается. Заметим, метод bind() регистрирует функцию как обработчик события, а метод unbind() отменяет регистрацию.
Рис. 6. Перемещение шариков по игровому полю.
from tkinter import *
from random import *
file = ['red.png', 'yellow.png', 'gold.png', 'green.png', 'emerald.png', 'cyan.png', 'blue.png', 'pink.png',
'azure.png', 'bronze.png', 'purple.png', 'scarlet.png', 'steel.png', 'silver.png']
colores = ['#aa9', '#776', '#ccd'] # Цвета поля
SIDE=7; SIZE=60; R=25 # Размер поля в клетках, клетки в пикселях, радиус шарика
playArea = [0]*SIDE*SIDE # Игровое поле
for i in [0, 1, 5, 6, 7, 8, 12, 13, 35, 36, 40, 41, 42, 43, 47, 48]: playArea[i]=-1
def ifplay(event): # Разрешение движения
global num, dx, dy
num = event.x // SIZE + event.y // SIZE * SIDE # Исходная клетка
if playArea[num] > 0: # Если клетка с шариком
dx = int(event.x - cnv.coords(playArea[num])[0]) # Смещение курсора
dy = int(event.y - cnv.coords(playArea[num])[1]) # относительно шарика
cnv.tkraise(playArea[num]) # Поднять шарик на верхний слой
cnv.bind("<B1-Motion>", play) # Начать движение
cnv.bind("<B1-ButtonRelease>", moveEnd) # Конец движения
def play(event): # Начало движения
x = int(event.x-cnv.coords(playArea[num])[0]-dx)
y = int(event.y-cnv.coords(playArea[num])[1]-dy)
cnv.move(playArea[num], x, y)
def moveEnd(event): # Конец движения
num2 = event.x // SIZE + event.y // SIZE * SIDE # Целевая клетка
if playArea[num2] == 0 and playArea[int((num+num2)/2)] > 0 and ((abs(num2-num) == 2 and num2//SIDE-num//SIDE == 0) or (abs(num2-num) == 2*SIDE)):
x = num2 % SIDE * SIZE - cnv.coords(playArea[num])[0] + SIZE / 2
y = num2 // SIDE * SIZE - cnv.coords(playArea[num])[1] + SIZE / 2
cnv.move(playArea[num], x, y)
cnv.delete(playArea[int((num+num2)/2)])
playArea[int((num+num2)/2)] = 0
playArea[num2], playArea[num] = playArea[num], 0
else:
x = num % SIDE * SIZE - cnv.coords(playArea[num])[0] + SIZE / 2
y = num // SIDE * SIZE - cnv.coords(playArea[num])[1] + SIZE / 2
cnv.move(playArea[num], x, y)
cnv.unbind("<B1-Motion>")
cnv.unbind("<B1-ButtonRelease>")
def newGame(A=[10, 16, 17, 18, 24, 31]):
global playArea, file, img
img = [PhotoImage(file=i) for i in file]
playArea = list(map(lambda x: x if x<0 else 0, playArea)) # Очистка поля
cnv.delete('allBalls') # Удаление шариков
for i in A: # Размещение шариков
x = (i % SIDE) * SIZE + SIZE / 2
y = (i // SIDE) * SIZE + SIZE / 2
playArea[i] = cnv.create_image(x, y, image=choice(img), anchor=CENTER, tag='allBalls')
cnv = Canvas(width=SIDE*SIZE, height=SIDE*SIZE) # Объект класса Canvas с именем cnv
cnv.bind('<Button-1>', ifplay)
cnv.pack()
for y in range(SIDE): # Отрисовка поля
for x in range(SIDE):
cnv.create_rectangle(x*SIZE, y*SIZE, (x+1)*SIZE, (y+1)*SIZE,
fill=colores[0 if playArea[x+y*SIDE] == 0 else 1], outline=colores[2], width=1)
newGame()
mainloop()
Программа 5. Добавлены правила игры.
В программу 5, в функцию moveEnd() мы добавили проверку законности хода игрока. Если игрок опустил шарик в клетку, в которую ходить по правилам нельзя, шарик возвращается в исходную позицию.
Рис. 7. Игра окончена, на поле остался один шарик.
#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
#
# Peg.py
# Copyright (C) 2021 Aleksandr Diorditsa, see <https://adior.ru>
# 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.
#
# peg.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 *
file = ['red.png', 'yellow.png', 'gold.png', 'green.png', 'emerald.png', 'cyan.png', 'blue.png', 'pink.png',
'azure.png', 'bronze.png', 'purple.png', 'scarlet.png', 'steel.png', 'silver.png']
colores = ['#aa9', '#776', '#ccd'] # Цвета поля
SIDE=7; SIZE=60; R=25 # Размер поля в клетках, клетки в пикселях, радиус шарика
playArea = [0]*SIDE*SIDE # Игровое поле
for i in [0, 1, 5, 6, 7, 8, 12, 13, 35, 36, 40, 41, 42, 43, 47, 48]: playArea[i]=-1
f = open('peg', 'r')
peg = f.read()
peg = eval('[' + peg + ']')
f.close()
fnGame = [lambda A=i: newGame(A) for i in peg]
def ifplay(event): # Разрешение движения
global num, dx, dy
num = event.x // SIZE + event.y // SIZE * SIDE # Исходная клетка
if playArea[num] > 0: # Если клетка с шариком
dx = int(event.x - cnv.coords(playArea[num])[0]) # Смещение курсора
dy = int(event.y - cnv.coords(playArea[num])[1]) # относительно шарика
cnv.tkraise(playArea[num]) # Поднять шарик на верхний слой
cnv.bind("<B1-Motion>", play) # Начать движение
cnv.bind("<B1-ButtonRelease>", moveEnd) # Конец движения
def play(event): # Начало движения
x = int(event.x-cnv.coords(playArea[num])[0]-dx)
y = int(event.y-cnv.coords(playArea[num])[1]-dy)
cnv.move(playArea[num], x, y)
def moveEnd(event): # Конец движения
num2 = event.x // SIZE + event.y // SIZE * SIDE # Целевая клетка
if playArea[num2] == 0 and playArea[int((num+num2)/2)] > 0 and ((abs(num2-num) == 2 and num2//SIDE-num//SIDE == 0) or (abs(num2-num) == 2*SIDE)):
x = num2 % SIDE * SIZE - cnv.coords(playArea[num])[0] + SIZE / 2
y = num2 // SIDE * SIZE - cnv.coords(playArea[num])[1] + SIZE / 2
cnv.move(playArea[num], x, y)
cnv.delete(playArea[int((num+num2)/2)])
playArea[int((num+num2)/2)] = 0
playArea[num2], playArea[num] = playArea[num], 0
else:
x = num % SIDE * SIZE - cnv.coords(playArea[num])[0] + SIZE / 2
y = num // SIDE * SIZE - cnv.coords(playArea[num])[1] + SIZE / 2
cnv.move(playArea[num], x, y)
cnv.unbind("<B1-Motion>")
cnv.unbind("<B1-ButtonRelease>")
def newGame(A=[10, 16, 17, 18, 24, 31]):
global playArea, file, img
img = [PhotoImage(file=i) for i in file]
playArea = list(map(lambda x: x if x<0 else 0, playArea)) # Очистка поля
cnv.delete('allBalls') # Удаление шариков
for i in A: # Размещение шариков
x = (i % SIDE) * SIZE + SIZE / 2
y = (i // SIDE) * SIZE + SIZE / 2
playArea[i] = cnv.create_image(x, y, image=choice(img), anchor=CENTER, tag='allBalls')
frm = [Frame() for i in range(len(fnGame)//12 +1)]
for i in frm: i.pack()
for i in range(1, len(fnGame)):
Button(frm[(i//12)], text=chr(ord('A' if i<27 else 'G')+i-1), command=fnGame[i]).pack(side=LEFT)
cnv = Canvas(width=SIDE*SIZE, height=SIDE*SIZE) # Объект класса Canvas с именем cnv
cnv.bind('<Button-1>', ifplay)
cnv.pack()
for y in range(SIDE): # Отрисовка поля
for x in range(SIDE):
cnv.create_rectangle(x*SIZE, y*SIZE, (x+1)*SIZE, (y+1)*SIZE,
fill=colores[0 if playArea[x+y*SIDE] == 0 else 1], outline=colores[2], width=1)
newGame()
mainloop()
Программа 6. Игра с загрузкой уровней из файла
В программу 4 мы добавили загрузку уровней игры из файла с именем peg, и кнопки выбора уровня игры. В качестве обработчика события "Нажатие на кнопку выбора уровня игры" мы зарегистрировали элемент списка функций fnGame который, в свою очередь, вызывает функцию newGame().
Рис. 8. Вариант расстановки шариков на игровом поле.
Рис. 8. Вариант расстановки шариков на игровом поле.
Рис. 8. Вариант расстановки шариков на игровом поле.
[24],
[10, 16, 17, 18, 24, 31],
[10, 17, 22, 23, 24, 25, 26, 31, 38],
[2, 3, 4, 9, 10, 11, 16, 17, 18, 23, 25],
[3, 9, 10, 11, 15, 16, 17, 18, 19, 24, 31, 37, 38, 39, 44, 45, 46],
[10, 16, 17, 18, 22, 23, 24, 25, 26, 28, 29, 30, 31, 32, 33, 34],
[3, 9, 10, 11, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 29, 30, 31, 32, 33, 37, 38, 39, 45],
[2, 3, 4, 9, 10, 11, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 37, 38, 39, 44, 45, 46],
[10, 15, 16, 18, 19, 22, 23, 25, 26, 29, 30, 32, 33, 38],
[10, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 30, 31, 32, 37, 38, 39, 44, 45, 46],
[2, 3, 4, 9, 10, 11, 16, 17, 18, 22, 23, 24, 25, 26, 30, 31, 32, 37, 38, 39, 44, 45, 46],
[15, 16, 18, 19, 22, 23, 24, 25, 26, 29, 30, 32, 33],
[10, 16, 17, 18, 22, 24, 26, 29, 30, 31, 32, 33, 37, 38, 39, 44, 46],
[10, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 33, 34, 37, 38, 39, 44, 46],
[9, 11, 14, 15, 19, 20, 22, 26, 30, 32, 37, 39],
[15, 16, 17, 18, 19, 22, 23, 25, 26, 29, 30, 31, 32, 33],
[10, 16, 17, 18, 23, 24, 25, 30, 31, 32, 38],
[10, 14, 15, 16, 18, 19, 20, 21, 22, 24, 26, 27, 29, 31, 33, 37, 39],
[10, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 30, 32, 33, 38, 45],
[3, 10, 15, 16, 18, 19, 22, 23, 25, 26, 29, 30, 32, 33, 38, 45],
[2, 3, 4, 9, 10, 11, 15, 19, 22, 23, 25, 26, 29, 33, 37, 38, 39, 44, 45, 46],
[3, 9, 10, 11, 15, 16, 18, 19, 21, 22, 26, 27, 29, 30, 32, 33, 37, 38, 39, 45],
[9, 10, 11, 14, 15, 16, 18, 19, 20, 21, 22, 26, 27, 29, 30, 32, 33, 37, 38, 39],
[2, 3, 4, 9, 10, 11, 14, 15, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 33, 34, 37, 38, 39, 44, 45, 46]
Модуль 1. Уровни игры. текстовый файл "peg".
Немецкий математик Г. Лейбниц (1646—1716гг) начинал игру на поле с одним шариком и перепрыгивая через лунку, не убирал, а ставил на нее шарик. Учёный говорил, что восстанавливать намного сложнее, чем разрушать. Каждый раз он ставил себе задачу создать из появляющихся на поле шариков красивую фигуру.
#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
#
# Peg.py
# Copyright (C) 2021 Aleksandr Diorditsa, see <https://adior.ru>
# 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.
#
# peg.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 *
file = ['red.png', 'yellow.png', 'gold.png', 'green.png', 'emerald.png', 'cyan.png', 'blue.png', 'pink.png',
'azure.png', 'bronze.png', 'purple.png', 'scarlet.png', 'steel.png', 'silver.png']
colores = ['#aa9', '#776', '#ccd'] # Цвета поля
SIDE=7; SIZE=60; R=25 # Размер поля в клетках, клетки в пикселях, радиус шарика
playArea = [0]*SIDE*SIDE # Игровое поле
for i in [0, 1, 5, 6, 7, 8, 12, 13, 35, 36, 40, 41, 42, 43, 47, 48]: playArea[i]=-1
btn = []; frm = []
def loadLevel(): # Загрузка уровней игры
global btn, frm
f = open('peg', 'r')
peg = f.read()
peg = eval('[' + peg + ']')
f.close()
fnGame = [lambda A=i: newGame(A) for i in peg]
for i in btn: i.destroy()
for i in frm: i.destroy()
btn.clear()
frm.clear()
frm = [Frame(frmT) for i in range(len(fnGame)//12 +1)]
for i in frm: i.pack()
btn = [Button(frm[(i//12)], text=chr(ord('A' if i<27 else 'G')+i-1), command=fnGame[i]) for i in range(len(fnGame))]
for i in btn: i.pack(side=LEFT)
def ifplay(event): # Разрешение движения
global num, dx, dy
num = event.x // SIZE + event.y // SIZE * SIDE # Исходная клетка
if playArea[num] > 0: # Если клетка с шариком
dx = int(event.x - cnv.coords(playArea[num])[0]) # Смещение курсора
dy = int(event.y - cnv.coords(playArea[num])[1]) # относительно шарика
cnv.tkraise(playArea[num]) # Поднять шарик на верхний слой
cnv.bind("<B1-Motion>", play) # Начать движение
cnv.bind("<B1-ButtonRelease>", moveEnd) # Конец движения
def play(event): # Начало движения
x = int(event.x-cnv.coords(playArea[num])[0]-dx)
y = int(event.y-cnv.coords(playArea[num])[1]-dy)
cnv.move(playArea[num], x, y)
def moveEnd(event): # Конец движения
num2 = event.x // SIZE + event.y // SIZE * SIDE # Целевая клетка
num3 = int((num+num2)/2) # Средняя клетка
if playArea[num2] == 0 and playArea[num3] == 0 and ((abs(num2-num) == 2 and num2//SIDE-num//SIDE == 0) or (abs(num2-num) == 2*SIDE)):
x = num2 % SIDE * SIZE - cnv.coords(playArea[num])[0] + SIZE / 2
y = num2 // SIDE * SIZE - cnv.coords(playArea[num])[1] + SIZE / 2
cnv.move(playArea[num], x, y)
cnv.delete(playArea[num3])
playArea[num3] = cnv.create_image(num3%SIDE*SIZE+SIZE/2, num3//SIDE*SIZE+SIZE/2, image=choice(img), anchor=CENTER, tag='allBalls')
playArea[num2], playArea[num] = playArea[num], 0
else:
x = num % SIDE * SIZE - cnv.coords(playArea[num])[0] + SIZE / 2
y = num // SIDE * SIZE - cnv.coords(playArea[num])[1] + SIZE / 2
cnv.move(playArea[num], x, y)
cnv.unbind("<B1-Motion>")
cnv.unbind("<B1-ButtonRelease>")
def newGame(A=[24]):
global playArea, file, img
img = [PhotoImage(file=i) for i in file]
playArea = list(map(lambda x: x if x<0 else 0, playArea)) # Очистка поля
cnv.delete('allBalls') # Удаление шариков
for i in A: # Размещение шариков
x = (i % SIDE) * SIZE + SIZE / 2
y = (i // SIDE) * SIZE + SIZE / 2
playArea[i] = cnv.create_image(x, y, image=choice(img), anchor=CENTER, tag='allBalls')
def save():
f = open('peg', 'a')
s = []
for i in range(SIDE**2):
if playArea[i]>0:
s.append(i)
s = ',\n' + str(s)
f.write(s)
f.close()
def rotate():
newArea = [0]*SIDE*SIDE
for y in range(SIDE):
for x in range(SIDE):
newArea[x+y*SIDE] = playArea[(SIDE - 1 - y) + SIDE * x]
A = []
for i in range(SIDE**2):
if newArea[i]>0:
A.append(i)
newGame(A)
frmT = Frame()
frmT.pack()
loadLevel()
cnv = Canvas(width=SIDE*SIZE, height=SIDE*SIZE) # Объект класса Canvas с именем cnv
cnv.bind('<Button-1>', ifplay)
cnv.pack()
frmB = Frame()
frmB.pack()
Button(frmB, text='Rotate', command=rotate).pack(side=LEFT)
Button(frmB, text='Save', command=save).pack(side=LEFT)
Button(frmB, text='Load', command=loadLevel).pack(side=LEFT)
for y in range(SIDE): # Отрисовка поля
for x in range(SIDE):
cnv.create_rectangle(x*SIZE, y*SIZE, (x+1)*SIZE, (y+1)*SIZE,
fill=colores[0 if playArea[x+y*SIDE] == 0 else 1], outline=colores[2], width=1)
newGame()
mainloop()
Программа 7. Редактор
Рис. 9. Редактор уровней игры