-
Notifications
You must be signed in to change notification settings - Fork 0
/
tk_snake.py
187 lines (159 loc) · 6.22 KB
/
tk_snake.py
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env python3
"""
Python version of the common game "Snake and apples"
"""
from random import randrange
from snake import Snake, Field
import tkinter as tk
from tkinter import messagebox
root = tk.Tk()
"""
Some constants that will be used: snake direction (str), number of apples to appear on the field (int),
speed of snake movements in ms (int), scale of the field (int), shift for one step (int),
colours of field (str), snake (str) and apple (str)
"""
DOWN = "DOWN"
UP = "UP"
RIGHT = "RIGHT"
LEFT = "LEFT"
NUMBER_OF_APPLES = 20
SNAKE_SPEED = 250
SCALE = SHIFT = 10
FIELD_COLOUR = 'green'
SNAKE_COLOUR = 'blue'
DIED_SNAKE_COLOUR = 'brown'
APPLE_COLOUR = 'red'
class Game:
"""
Information about the game process.
Attributes:
field (class) : class with parameters of the field for a game
snake (class) : class with parameters of snake
"""
def __init__(self, field, snake):
self._field = field
self._snake = snake
self._apple_position = tuple()
self._painted_apples = 0
self._direction_action = {
LEFT: lambda x, y: ((x - 1) % self._field.width(), y),
RIGHT: lambda x, y: ((x + 1) % self._field.width(), y),
UP: lambda x, y: (x, (y - 1) % self._field.height()),
DOWN: lambda x, y: (x, (y + 1) % self._field.height())
}
self._canvas = tk.Canvas(root, width=self._field.width()*SCALE, height=self._field.height()*SCALE,
bg=FIELD_COLOUR)
self._canvas.pack()
self._after_id = []
def put_apple(self):
"""
Randomly generates coordinate for an apple on the field
:return: False if no place for an apple
"""
self._apple_position = (randrange(self._field.width()), randrange(self._field.height()))
while self._apple_position in self._snake:
self._apple_position = (randrange(self._field.width()), randrange(self._field.height()))
def _move_snake(self, direction):
"""
Works with user input: transforms direction into snake_head coordinates (position)
:return: None
"""
position = self._direction_action[direction](*self._snake.head())
is_apple = position == self._apple_position
if is_apple:
self.put_apple()
self.paint_apple()
if len(self._snake) > 2 and position == self._snake.snake_coordinates()[-2]:
self._snake.revert()
else:
self._snake.move(position, is_apple)
self.paint_snake()
self._after_id.append(self._canvas.after(SNAKE_SPEED, self._move_snake, direction))
self._canvas.after_cancel(self._after_id.pop(0))
if self.check_win() or self.check_snake():
self._canvas.after_cancel(self._after_id.pop(0))
self.check_progress()
def paint_snake(self):
"""
Paints snake on the field
:return: None
"""
self._canvas.delete('snake')
for x, y in self._snake:
self._canvas.create_rectangle(x*SCALE, y*SCALE, x*SCALE+SHIFT, y*SCALE+SHIFT,
outline='white', fill=SNAKE_COLOUR, tag='snake')
if self._snake.snake_coordinates().count((x, y)) > 1:
self._canvas.create_rectangle(x*SCALE, y*SCALE, x*SCALE+SHIFT, y*SCALE+SHIFT,
outline='white', fill=DIED_SNAKE_COLOUR, tag='snake')
def check_snake(self):
"""
Checks snake accidents
:return: True when snake crashes
"""
return len(self._snake) != len(set(self._snake.snake_coordinates()))
def check_win(self):
"""
Checks winning conditional
:return: True when all apples are collected
"""
return len(self._snake) == NUMBER_OF_APPLES + 1
def check_progress(self):
"""
Stops the game, when something happen
:return: None
:raise: message window "YOU WIN! All apples are safely collected" when the game is finished
:raise: message window "GAME OVER! Your snake crashed" if snake moves into itself
"""
if self.check_snake():
but = tk.Button(root, text="Again?")
but.pack()
but.bind("<Button-1>", lambda event: [but.destroy(),self._canvas.destroy(), main()])
messagebox.showinfo("GAME OVER!", "Your snake crashed")
if self.check_win():
but = tk.Button(root, text="New level")
but.pack()
but.bind("<Button-1>", lambda event: [but.destroy(), self._canvas.destroy(), main()])
messagebox.showinfo("YOU WIN!", "All apples are safely collected")
def paint_apple(self):
"""
Paints an apple on the field, no more than NUMBER_OF_APPLES times for the game
:return: None
"""
self._canvas.delete('apple')
x, y = self._apple_position
if self._painted_apples < NUMBER_OF_APPLES:
self._canvas.create_oval(x*SCALE, y*SCALE, x*SCALE+SHIFT, y*SCALE+SHIFT, fill=APPLE_COLOUR, tag='apple')
self._painted_apples += 1
def paint(self):
"""
Prints field with apples and snake and creates buttons
:return: None
"""
self.paint_snake()
self.paint_apple()
root.mainloop()
def start(self):
"""
Starts and controls the game.
Binds arrow buttons with canvas, randomly puts apple on the field,
initiates snake movements, paints all on the window, asks user for snake movement direction
"""
self._canvas.focus_set()
self._canvas.bind('<Left>', lambda event: self._move_snake(LEFT))
self._canvas.bind('<Right>', lambda event: self._move_snake(RIGHT))
self._canvas.bind('<Up>', lambda event: self._move_snake(UP))
self._canvas.bind('<Down>', lambda event: self._move_snake(DOWN))
self.put_apple()
self._after_id.append(self._canvas.after(SNAKE_SPEED, self._move_snake, RIGHT))
self.paint()
def main():
"""
Create objects of all classes and starts the game
:return: None
"""
field = Field(10, 10)
snake = Snake((0, 0))
game = Game(field, snake)
game.start()
if __name__ == '__main__':
main()