Игра
28.02.2021, 20:08. Показов 5157. Ответов 0
Игрок в ней ходит по сетке размером N_X на N_Y с шагом step. Ему нужно добраться до выхода, причем начальное положение игрока и выхода определяется случайно.
Шаблон программы уже есть:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| import tkinter
import random
def move_wrap(obj, move):
canvas.move(obj, move[0], move[1])
# Здесь нужно сделать так, чтобы ушедший
# "за экран" игрок выходил с другой стороны
def check_move():
if canvas.coords(player) == canvas.coords(exit):
label.config(text="Победа!")
def key_pressed(event):
if event.keysym == 'Up':
move_wrap(player, (0, -step))
# Здесь нужно дописать то, что нужно,
# чтобы все остальные клавиши работали
check_move()
master = tkinter.Tk()
step = 60
N_X = 10
N_Y = 10
canvas = tkinter.Canvas(master, bg='blue',
width=step * N_X, height=step * N_Y)
player_pos = (random.randint(0, N_X - 1) * step,
random.randint(0, N_Y - 1) * step)
exit_pos = (random.randint(0, N_X - 1) * step,
random.randint(0, N_Y - 1) * step)
player = canvas.create_oval((player_pos[0], player_pos[1]),
(player_pos[0] + step, player_pos[1] + step),
fill='green')
exit = canvas.create_oval((exit_pos[0], exit_pos[1]),
(exit_pos[0] + step, exit_pos[1] + step),
fill='yellow')
label = tkinter.Label(master, text="Найди выход")
label.pack()
canvas.pack()
master.bind("<KeyPress>", key_pressed)
master.mainloop() |
|
Пока есть одна проблема: когда игрок находит выход, ничего не происходит. Он может гулять и дальше. Чтобы исправить это, можно связать с событием какую-нибудь другую функцию. Пусть эта функция ничего не делает. Определим ее и добавим в функцию check_move одну строчку:
| Python | 1
2
3
4
5
6
7
8
| def do_nothing(x):
pass
def check_move():
if canvas.coords(player) == canvas.coords(exit):
label.config(text="Победа!")
master.bind("<KeyPress>", do_nothing) |
|
Функция do_nothing принимает на вход один аргумент. Функции, которые используются в команде bind, тоже всегда получают на вход один аргумент — описание события. Если бы мы определили функцию do_nothing() без параметров, то в процессе выполнения программы получили бы ошибку.
Теперь, попадая к выходу, игрок теряет управление, и нам остается только закрыть окно. Наверное, стоит добавить кнопку, которая позволит ему начать сначала.
Создание кнопки
Кнопка создается командой tkinter.Button(...)
В качестве параметров ей нужно передать окно, в котором будет создаваться кнопка; текст, который будет написан на кнопке и функцию, которая вызывается при ее нажатии.
Например так:
| Python | 1
2
3
| restart = tkinter.Button(master, text="Начать заново",
command=prepare_and_start)
restart.pack() |
|
Теперь стоит перенести в отдельную функцию код, подготавливающий игровое поле:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| def prepare_and_start():
global player, exit
player_pos = (random.randint(1, N_X - 1) * step,
random.randint(1, N_Y - 1) * step)
exit_pos = (random.randint(1, N_X - 1) * step,
random.randint(1, N_Y - 1) * step)
player = canvas.create_oval(
(player_pos[0], player_pos[1]),
(player_pos[0] + step, player_pos[1] + step),
fill='green')
exit = canvas.create_oval(
(exit_pos[0], exit_pos[1]),
(exit_pos[0] + step, exit_pos[1] + step),
fill='yellow')
label.config(text="Найди выход!")
master.bind("<KeyPress>", key_pressed) |
|
Команда bind работает с функциями, получающими ровно один аргумент — событие.
Поскольку наша программа невелика, мы решим эту проблему за счет глобальных переменных.
Основной код программы теперь выглядит так:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| step = 60 # Размер клетки
N_X = 10
N_Y = 10 # Размер сетки
master = tkinter.Tk()
label = tkinter.Label(master, text="Найди выход")
label.pack()
canvas = tkinter.Canvas(master, bg='blue',
height=N_X * step, width=N_Y * step)
canvas.pack()
restart = tkinter.Button(master, text="Начать заново",
command=prepare_and_start)
restart.pack()
prepare_and_start()
master.mainloop() |
|
При попытке запустить новую программу появляется новая проблема: после нажатия Начать заново игрок и «выход» не исчезают. Нужно добавить в функцию prepare_and_start удаление всех старых объектов. К счастью, это можно сделать одной командой: canvas.delete("all"). надо добавить ее в программу.
В игру уже можно играть, но игроку чересчур легко живется: он даже проиграть не может! надо добавить препятствия: например, огонь, в который нельзя наступать.
Для этого придется переписать функцию prepare_and_start:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| def prepare_and_start():
global player, exit, fires
canvas.delete("all")
player_pos = (random.randint(1, N_X - 1) * step,
random.randint(1, N_Y - 1) * step)
exit_pos = (random.randint(1, N_X - 1) * step,
random.randint(1, N_Y) * step)
player = canvas.create_oval(
(player_pos[0], player_pos[1]),
(player_pos[0] + step, player_pos[1] + step),
fill='green')
exit = canvas.create_oval(
(exit_pos[0], exit_pos[1]),
(exit_pos[0] + step, exit_pos[1] + step),
fill='yellow')
N_FIRES = 6 # Число клеток, заполненных огнем
fires = []
for i in range(N_FIRES):
fire_pos = (random.randint(1, N_X - 1) * step,
random.randint(1, N_Y - 1) * step)
fire = canvas.create_oval(
(fire_pos[0], fire_pos[1]),
(fire_pos[0] + step, fire_pos[1] + step),
fill='red')
fires.append(fire)
label.config(text="Найди выход!")
master.bind("<KeyPress>", key_pressed) |
|
И функцию, проверяющую результат хода:
| Python | 1
2
3
4
5
6
7
8
| def check_move():
if canvas.coords(player) == canvas.coords(exit):
label.config(text="Победа!")
master.bind("<KeyPress>", do_nothing)
for f in fires:
if canvas.coords(player) == canvas.coords(f):
label.config(text="Ты проиграл!")
master.bind("<KeyPress>", do_nothing) |
|
Игра уже почти как настоящая. Осталось два штриха:
Улучшить графику и
Добавить еще врагов
Добавление графики
На холст (Canvas) можно добавить любую картинку. В зависимости от типа изображения код будет немного варьироваться, мы будем рассматривать работу с изображениями в формате gif. Сначала картинку нужно загрузить с помощью функции tkinter.PhotoImage, а затем создать на холсте:
player_pic = tkinter.PhotoImage(file="doctor.gif")
player = canvas.create_image((player_pos[0], player_pos[1]),
image=player_pic, anchor='nw')
Параметр anchor='nw' означает, что в указанную первым параметром координату помещается левый верхний (буквально — северо-западный, по-английски — north-west) угол картинки. Если этот параметр не указать, то картинка будет центрирована по заданной координате. Можно добавить картинки для всех объектов в основную часть кода и в функцию prepare_and_start().
В основную часть кода:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| player_pic = tkinter.PhotoImage(file="https://www.cyberforum.ru/images/doctor.gif")
exit_pic = tkinter.PhotoImage(file="https://www.cyberforum.ru/images/tardis.gif")
fire_pic = tkinter.PhotoImage(file="https://www.cyberforum.ru/images/fire.gif")
enemy_pic = tkinter.PhotoImage(file="https://www.cyberforum.ru/images/dalek.gif")
И в функцию prepare_and_start:
def prepare_and_start():
global player, exit, fires
canvas.delete("all")
player_pos = (random.randint(1, N_X - 1) * step,
random.randint(1, N_Y - 1) * step)
player = canvas.create_image(
(player_pos[0], player_pos[1]), image=player_pic, anchor='nw')
exit_pos = (random.randint(1, N_X - 1) * step,
random.randint(1, N_Y - 1) * step)
exit = canvas.create_image(
(exit_pos[0], exit_pos[1]), image=exit_pic, anchor='nw')
N_FIRES = 6 # Число клеток, заполненных огнем
fires = []
for i in range(N_FIRES):
fire_pos = (random.randint(1, N_X - 1) * step,
random.randint(1, N_Y - 1) * step)
# fire = canvas.create_oval((fire_pos[0],fire_pos[1]),
# (fire_pos[0] + step, fire_pos[1] + step), fill='red')
fire = canvas.create_image(
(fire_pos[0], fire_pos[1]), image=fire_pic, anchor='nw')
fires.append(fire)
label.config(text="Найди выход!")
master.bind("<KeyPress>", key_pressed) |
|
Картинки можно выбрать свои — они должны быть в формате gif и иметь размер step*step пикселей. Желательно так же делать их на прозрачном фоне.
Ну и последний штрих. Добавить настоящих врагов, которые тоже могут двигаться. Создадим их в функции prepare_and_start(), немного модифицировав последнюю:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| def prepare_and_start():
global player, exit, fires, enemies
canvas.delete("all")
player_pos = (random.randint(0, N_X - 1) * step,
random.randint(0, N_Y - 1) * step)
player = canvas.create_image(player_pos, image=player_pic, anchor='nw')
exit_pos = (random.randint(0, N_X - 1) * step,
random.randint(0, N_Y - 1) * step)
exit = canvas.create_image(exit_pos, image=exit_pic, anchor='nw')
N_FIRES = 6 #Число клеток, заполненных огнем
fires = []
for i in range(N_FIRES):
fire_pos = (random.randint(0, N_X - 1) * step,
random.randint(0, N_Y - 1) * step)
fire = canvas.create_image(fire_pos, image=fire_pic, anchor='nw')
fires.append(fire)
N_ENEMIES = 4 #Число врагов
enemies = []
for i in range(N_ENEMIES):
enemy_pos = (random.randint(0, N_X - 1) * step,
random.randint(0, N_Y - 1) * step)
enemy = canvas.create_image(enemy_pos, image=enemy_pic, anchor='nw')
enemies.append((enemy, random.choice([always_right, random_move])))
label.config(text="Найди выход!")
master.bind("<KeyPress>", key_pressed) |
|
Каждый враг в нашей программе будет представлен парой (объект на Canvas + функция движения). Определим для начала две таких функции:
| Python | 1
2
3
4
5
6
| def always_right():
return (step, 0)
def random_move():
return random.choice([(step, 0), (-step, 0), (0, step), (0, -step)]) |
|
Модифицируем функцию key_pressed: ее нужно дополнить перемещением врагов — вот таким фрагментом кода:
| Python | 1
2
3
| for enemy in enemies:
direction = enemy[1]() # вызвать функцию перемещения у "врага"
move_wrap(enemy[0], direction) # произвести перемещение |
|
Кроме того, нужно переписать функцию check_move:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
| def check_move():
if canvas.coords(player) == canvas.coords(exit):
label.config(text="Победа!")
master.bind("<KeyPress>", do_nothing)
for f in fires:
if canvas.coords(player) == canvas.coords(f):
label.config(text="Ты проиграл!")
master.bind("<KeyPress>", do_nothing)
for e in enemies:
if canvas.coords(player) == canvas.coords(e[0]):
label.config(text="Ты проиграл!")
master.bind("<KeyPress>", do_nothing) |
|
0
|