├── requirements.txt
├── fonts
├── ARCADE.TTF
└── Roboto
│ ├── Roboto-Bold.ttf
│ └── Roboto-Regular.ttf
├── resources
└── demo.gif
├── theme_settings
├── __init__.py
├── font_registry.py
└── theme_registry.py
├── LICENSE
├── README.md
└── snake_game.py
/requirements.txt:
--------------------------------------------------------------------------------
1 | dearpygui==1.0.2
--------------------------------------------------------------------------------
/fonts/ARCADE.TTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RahulShagri/OG-Snake-Game/HEAD/fonts/ARCADE.TTF
--------------------------------------------------------------------------------
/resources/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RahulShagri/OG-Snake-Game/HEAD/resources/demo.gif
--------------------------------------------------------------------------------
/fonts/Roboto/Roboto-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RahulShagri/OG-Snake-Game/HEAD/fonts/Roboto/Roboto-Bold.ttf
--------------------------------------------------------------------------------
/theme_settings/__init__.py:
--------------------------------------------------------------------------------
1 | from theme_settings.font_registry import *
2 | from theme_settings.theme_registry import *
--------------------------------------------------------------------------------
/fonts/Roboto/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RahulShagri/OG-Snake-Game/HEAD/fonts/Roboto/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/theme_settings/font_registry.py:
--------------------------------------------------------------------------------
1 | # Add all fonts
2 | import dearpygui.dearpygui as dpg
3 |
4 | with dpg.font_registry() as main_font_registry:
5 | regular_font = dpg.add_font('fonts/Roboto/Roboto-Regular.ttf', 16)
6 | bold_font = dpg.add_font('fonts/Roboto/Roboto-Bold.ttf', 21)
7 | score_font = dpg.add_font('fonts/ARCADE.ttf', 35)
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 RahulShagri
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/theme_settings/theme_registry.py:
--------------------------------------------------------------------------------
1 | # Add themes
2 | import dearpygui.dearpygui as dpg
3 |
4 | with dpg.theme() as global_theme:
5 | with dpg.theme_component(dpg.mvAll):
6 | # Styles
7 | dpg.add_theme_style(dpg.mvStyleVar_WindowPadding, 4, 4, category=dpg.mvThemeCat_Core)
8 | dpg.add_theme_style(dpg.mvStyleVar_ItemSpacing, 4, 4, category=dpg.mvThemeCat_Core)
9 | dpg.add_theme_style(dpg.mvStyleVar_ChildRounding, 4, 4, category=dpg.mvThemeCat_Core)
10 | dpg.add_theme_style(dpg.mvStyleVar_FrameRounding, 4, 4, category=dpg.mvThemeCat_Core)
11 | dpg.add_theme_style(dpg.mvStyleVar_ChildBorderSize, 0, category=dpg.mvThemeCat_Core)
12 |
13 | # Colors
14 | dpg.add_theme_color(dpg.mvThemeCol_WindowBg, (33, 33, 33), category=dpg.mvThemeCat_Core)
15 | dpg.add_theme_color(dpg.mvThemeCol_MenuBarBg, (48, 48, 48), category=dpg.mvThemeCat_Core)
16 | dpg.add_theme_color(dpg.mvThemeCol_Text, (200, 200, 200), category=dpg.mvThemeCat_Core)
17 |
18 | with dpg.theme() as disabled_theme:
19 | with dpg.theme_component(dpg.mvAll):
20 | # Styles
21 |
22 | # Colors
23 | dpg.add_theme_color(dpg.mvThemeCol_Text, (100, 100, 100), category=dpg.mvThemeCat_Core)
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # :snake: Snake Game
2 |
3 | 
4 |
5 | Maneuver a snake in its burrow and earn points while avoiding the snake itself and the walls of the snake burrow. The snake grows when it eats an apple by default which can be disabled in the settings tab where you can also find all the other customization options.
6 |
7 | Game developed purely on python using the [Dear PyGui Framework](https://github.com/hoffstadt/DearPyGui).
8 |
9 |
:book: Instructions
10 |
11 | 1. Make sure you have Python 3 installed and working.
12 |
13 | 2. Clone the repo:
14 |
15 | ```git clone https://github.com/RahulShagri/OG-Snake-Game.git```
16 |
17 | 3. Install prerequisites using pip, preferably in a new environment:
18 |
19 | ```pip install -r requirements.txt```
20 |
21 | 4. Run the snake_game.py file to start the application.
22 |
23 | 5. Customize the speed and colors and hit the `Start` button to start the game. Maneuver the snake by using the arrow keys or the W, S, D, and A keys on your keyboard. Avoid hitting the snake's body or the walls of the burrow.
24 |
25 | 6. Hit the `Restart` button to restart the game without resetting the highest score. Hit the `Reset Stats` button to reset all the scores.
26 |
27 | 7. Use the `Fix snake length` checkbox to stop the snake from growing when it eats an apple.
28 |
--------------------------------------------------------------------------------
/snake_game.py:
--------------------------------------------------------------------------------
1 | import dearpygui.dearpygui as dpg
2 |
3 | dpg.create_context()
4 |
5 | import threading
6 | import time
7 | import random
8 | import webbrowser
9 | from theme_settings import *
10 |
11 |
12 | slither_data = [] # List of points and their respective direction. [[x_coordinate, y_coordinate], direction]
13 | slither_change_data = [] # List of change of directions. [[x_coordinate, y_coordinate], direction]
14 | snake_burrow = None # the plot item of the main display of the game
15 | snake = None # the polyline item which acts as the snake
16 | snake_moving_flag = 0 # flag to check if snake is moving or not
17 | snake_length_flag = 1 # Flag to check if the snake should grow or not. 1=grow 0=fixed
18 | fix_snake_length = None
19 |
20 | apple = None # Apple item in DPG
21 | apple_points = [] # Every unit coordinate of the apple rectangle. If the snake passes through any of these
22 | # coordinate, then the apple changes location
23 |
24 | snake_speed = None
25 | snake_color = [0, 255, 0]
26 | apple_color = [255, 0, 0]
27 | burrow_color = [33, 33, 33]
28 | burrow = None # Burrow (background) item in DPG
29 |
30 | score = None # Score item in DPG
31 | score_count = 0
32 | highest_score = None # Highest score item in DPG
33 | highest_score_count = 0
34 |
35 |
36 | def initial_slither_points():
37 | # Function sets all the points required to draw the snake initially
38 | global slither_data, snake
39 |
40 | slither_data = []
41 |
42 | head_point = [25, 25]
43 | tail_point = [25, 10]
44 | snake_length = head_point[1] - tail_point[1]
45 |
46 | for point in range(snake_length):
47 | slither_data.append([[head_point[0], (head_point[1] - point)], 2])
48 |
49 | slither_data.append([tail_point, 2])
50 |
51 |
52 | def restart_snake():
53 | global slither_data, slither_change_data, snake, snake_moving_flag, score, score_count
54 |
55 | slither_data = []
56 | slither_change_data = []
57 | snake_moving_flag = 0
58 | score_count = 0
59 | dpg.set_value(item=score, value=score_count)
60 |
61 | initial_slither_points()
62 | place_apple()
63 |
64 | dpg.configure_item(item=snake, points=get_points_from_data(slither_data), color=dpg.get_value(item=snake_color))
65 |
66 |
67 | def move_snakeDispatcher():
68 | # Function creates a new thread that controls the continuous movement of the snake while the main code is listening
69 | # for any keyboard or mouse events to occur
70 | move_snake_thread = threading.Thread(name="move snake", target=move_snake, args=(), daemon=True)
71 | move_snake_thread.start()
72 |
73 |
74 | def move_snake():
75 | global slither_data, slither_change_data, snake, snake_moving_flag, apple_points, snake_speed, snake_color, \
76 | snake_length_flag, score, score_count, highest_score, highest_score_count
77 |
78 | snake_moving_flag = 1
79 |
80 | while True:
81 | body_points = get_points_from_data(slither_data)
82 | body_points.pop(0) # List of all points of the snake except the head
83 |
84 | if slither_data[0][0][1] == 50 or slither_data[0][0][0] == 50 or \
85 | slither_data[0][0][1] == 0 or slither_data[0][0][0] == 0 or \
86 | slither_data[0][0] in body_points: # Check if the snake touches the walls or itself
87 |
88 | for i in range(2):
89 | dpg.configure_item(item=snake, color=[255, 0, 0])
90 | time.sleep(0.15)
91 | dpg.configure_item(item=snake, color=dpg.get_value(item=snake_color))
92 | time.sleep(0.15)
93 |
94 | dpg.configure_item(item=snake, color=[255, 0, 0, 255])
95 |
96 | snake_moving_flag = 0
97 | break # End Game
98 |
99 | if slither_data[0][0] in apple_points:
100 | # If the head of the snake passes through any of the apple coordinates, then the apple changes location
101 | # and the snake's tail gets longer
102 | apple_points = []
103 | place_apple()
104 |
105 | score_count += 1
106 | dpg.set_value(item=score, value=score_count)
107 |
108 | if score_count > highest_score_count:
109 | highest_score_count = score_count
110 | dpg.set_value(item=score, value=score_count)
111 | dpg.set_value(item=highest_score, value=highest_score_count)
112 |
113 | if snake_length_flag == 1: # Only if user wants to grow the snake
114 | add_length = 3 # Add additional 3 points to the tail
115 | tail_direction = slither_data[-1][1] # Direction the tail is moving in
116 | tail_point = slither_data[-1][0][:]
117 |
118 | if tail_direction == 1:
119 | for n in range(add_length):
120 | new_tail_point = [tail_point[0] + n + 1, tail_point[1]]
121 | slither_data.append([new_tail_point, tail_direction])
122 |
123 | elif tail_direction == 2:
124 | for n in range(add_length):
125 | new_tail_point = [tail_point[0], tail_point[1] - 1 - n]
126 | slither_data.append([new_tail_point, tail_direction])
127 |
128 | elif tail_direction == 3:
129 | for n in range(add_length):
130 | new_tail_point = [tail_point[0] - 1 - n, tail_point[1]]
131 | slither_data.append([new_tail_point, tail_direction])
132 |
133 | else:
134 | for n in range(add_length):
135 | new_tail_point = [tail_point[0], tail_point[1] + 1 + n]
136 | slither_data.append([new_tail_point, tail_direction])
137 |
138 | else:
139 | for index in range(len(slither_data)):
140 | # This loop controls the continuous motion of the snake
141 | if slither_data[index][1] == 1:
142 | # Move West. Subtract X
143 | slither_data[index][0][0] -= 1
144 |
145 | elif slither_data[index][1] == 2:
146 | # Move North. Add Y
147 | slither_data[index][0][1] += 1
148 |
149 | elif slither_data[index][1] == 3:
150 | # Move East. Add X
151 | slither_data[index][0][0] += 1
152 |
153 | elif slither_data[index][1] == 4:
154 | # Move South. Subtract Y
155 | slither_data[index][0][1] -= 1
156 |
157 | if slither_data[index][0] in get_points_from_data(slither_change_data):
158 | # If the point of the snake is found in the list of direction of changes, then get the direction
159 | # of that point gets updated
160 | slither_data[index][1] = get_direction_from_data(slither_data[index][0], slither_change_data)
161 |
162 | for index in range(len(slither_change_data)):
163 | if not slither_change_data[index][0] in get_points_from_data(slither_data):
164 | slither_change_data.pop(index)
165 | break
166 |
167 | dpg.configure_item(item=snake, points=get_points_from_data(slither_data))
168 | time.sleep((-0.01*dpg.get_value(item=snake_speed)) + 0.13) # Sets the speed of the snake depending on the value
169 |
170 | # Corresponding time values in seconds
171 | # Equation used: speed = -0.01*(value input) + 0.13
172 | # 0.12 = 1
173 | # 0.11 = 2
174 | # 0.1 = 3
175 | # 0.09 = 4
176 | # 0.08 = 5
177 | # 0.07 = 6
178 | # 0.06 = 7
179 | # 0.05 = 8
180 | # 0.04 = 9
181 | # 0.03 = 10
182 |
183 |
184 | def get_points_from_data(data):
185 | # Functions takes entire data of slither and returns only the points
186 | slither_points = []
187 | for index in range(len(data)):
188 | slither_points.append(data[index][0])
189 |
190 | return slither_points
191 |
192 |
193 | def get_direction_from_data(point, data):
194 | # Functions takes the entire data and extracts a particular direction for a given point
195 | for index in range(len(data)):
196 | if point == data[index][0]:
197 | return data[index][1]
198 |
199 |
200 | def place_apple():
201 | global slither_data, apple
202 |
203 | pos_flag = 0
204 |
205 | while True:
206 | # Keeps looping until it finds a location (all points of the rectangle) that is not on the snake
207 | x_pos = random.randint(0, 47)
208 | y_pos = random.randint(0, 47)
209 |
210 | for x in range(x_pos, x_pos+3):
211 | for y in range(y_pos, y_pos+3):
212 | if [x, y] in get_points_from_data(slither_data):
213 | pos_flag = 1 # Signal that a point was found and continue finding a random point
214 | break
215 | else:
216 | continue
217 | break
218 |
219 | if pos_flag == 1:
220 | # If a point is found inside the snake, then reset the pos_flag and continue iterating until
221 | # a new point outside the snake is found
222 | pos_flag = 0
223 | continue
224 |
225 | if pos_flag == 0: # If pos_flag is not 1, continue placing the apple, otherwise reiterate
226 | dpg.configure_item(item=apple, pmin=[x_pos, y_pos], pmax=[x_pos+2, y_pos+2])
227 | for x in range(x_pos, x_pos + 3):
228 | for y in range(y_pos, y_pos + 3):
229 | apple_points.append([x, y])
230 | return
231 |
232 |
233 | def change_colors():
234 | global snake_color, apple_color, burrow_color, snake, apple, burrow
235 |
236 | dpg.configure_item(item=snake, color=dpg.get_value(item=snake_color))
237 | dpg.configure_item(item=apple, color=dpg.get_value(item=apple_color), fill=dpg.get_value(item=apple_color))
238 | dpg.configure_item(item=burrow, color=dpg.get_value(item=burrow_color), fill=dpg.get_value(item=burrow_color))
239 |
240 |
241 | def check_snake_length():
242 | global snake_length_flag
243 | if dpg.get_value(item=fix_snake_length):
244 | snake_length_flag = 0
245 |
246 | else:
247 | snake_length_flag = 1
248 |
249 |
250 | def reset_stats():
251 | global score, score_count, highest_score, highest_score_count
252 |
253 | score_count = 0
254 | highest_score_count = 0
255 | dpg.set_value(item=score, value=score_count)
256 | dpg.set_value(item=highest_score, value=highest_score_count)
257 |
258 |
259 | def reset_settings():
260 | global snake, snake_color, apple, apple_color, burrow, burrow_color, snake_speed, fix_snake_length
261 | global snake_length_flag
262 |
263 | dpg.configure_item(item=snake, color=[0, 255, 0])
264 | dpg.configure_item(item=apple, color=[255, 0, 0], fill=[255, 0, 0])
265 | dpg.configure_item(item=burrow, color=[33, 33, 33], fill=[33, 33, 33])
266 |
267 | dpg.configure_item(item=snake_color, default_value=[0, 255, 0])
268 | dpg.configure_item(item=apple_color, default_value=[255, 0, 0])
269 | dpg.configure_item(item=burrow_color, default_value=[33, 33, 33])
270 |
271 | dpg.configure_item(item=snake_speed, default_value=5)
272 | dpg.set_value(item=fix_snake_length, value=False)
273 | snake_length_flag = 1
274 |
275 |
276 | def open_help():
277 | webbrowser.open("https://github.com/RahulShagri/OG-Snake-Game")
278 |
279 |
280 | def key_release_handler(sender, app_data):
281 | # Function listening to key release events. Arrow keys change snake direction and keeps a track of the point when
282 | # the key event occurs
283 |
284 | if snake_moving_flag == 0: # If snake not moving then exit
285 | return
286 |
287 | global slither_data, slither_change_data
288 | head_point = slither_data[0][0][:]
289 | head_direction = slither_data[0][1]
290 |
291 | if head_direction == 1:
292 | # West
293 | head_point[0] -= 1
294 |
295 | if head_direction == 2:
296 | # North
297 | head_point[1] += 1
298 |
299 | if head_direction == 3:
300 | # East
301 | head_point[0] += 1
302 |
303 | if head_direction == 4:
304 | # South
305 | head_point[1] -= 1
306 |
307 | if app_data == 37 or app_data == 65:
308 | # West
309 | if head_direction != 3 and head_direction != 1:
310 | snake_direction = 1
311 | slither_change_data.append([head_point, snake_direction])
312 |
313 | if app_data == 38 or app_data == 87:
314 | # North
315 | if head_direction != 4 and head_direction != 2:
316 | snake_direction = 2
317 | slither_change_data.append([head_point, snake_direction])
318 |
319 | if app_data == 39 or app_data == 68:
320 | # East
321 | if head_direction != 1 and head_direction != 3:
322 | snake_direction = 3
323 | slither_change_data.append([head_point, snake_direction])
324 |
325 | if app_data == 40 or app_data == 83:
326 | # South
327 | if head_direction != 2 and head_direction != 4:
328 | snake_direction = 4
329 | slither_change_data.append([head_point, snake_direction])
330 |
331 |
332 | def main_window_setup():
333 | global snake, snake_burrow, apple, snake_speed, snake_color, apple_color, burrow_color, burrow
334 | global fix_snake_length, score, highest_score
335 |
336 | dpg.create_viewport(title="Snake Game", x_pos=0, y_pos=0, width=750, height=645)
337 | dpg.set_viewport_max_height(645)
338 | dpg.set_viewport_max_width(750)
339 |
340 | with dpg.window(pos=[0, 0], autosize=True, no_collapse=True, no_resize=True, no_close=True, no_move=True,
341 | no_title_bar=True) as main_window:
342 |
343 | with dpg.child_window(height=90, autosize_x=True):
344 |
345 | with dpg.group(horizontal=True):
346 | score_text = dpg.add_text(default_value=" Score : ")
347 | score = dpg.add_text(default_value="0")
348 |
349 | dpg.bind_item_font(item=score_text, font=score_font)
350 | dpg.bind_item_font(item=score, font=score_font)
351 |
352 | with dpg.group(horizontal=True):
353 | highest_score_text = dpg.add_text(default_value=" Highest score : ")
354 | highest_score = dpg.add_text(default_value="0")
355 |
356 | dpg.bind_item_font(item=highest_score_text, font=score_font)
357 | dpg.bind_item_font(item=highest_score, font=score_font)
358 |
359 | with dpg.group(horizontal=True):
360 | with dpg.group():
361 | with dpg.plot(no_menus=False, no_title=True, no_box_select=True, no_mouse_pos=True, width=500,
362 | height=500, equal_aspects=True) as snake_burrow:
363 | default_x = dpg.add_plot_axis(axis=0, no_gridlines=True, no_tick_marks=True, no_tick_labels=True,
364 | label="", lock_min=True)
365 | dpg.set_axis_limits(axis=default_x, ymin=0, ymax=50)
366 | default_y = dpg.add_plot_axis(axis=1, no_gridlines=True, no_tick_marks=True, no_tick_labels=True,
367 | label="", lock_min=True)
368 | dpg.set_axis_limits(axis=default_y, ymin=0, ymax=50)
369 |
370 | burrow = dpg.draw_rectangle(pmin=[0, 0], pmax=[50, 50], color=[33, 33, 33], fill=[33, 33, 33])
371 | snake = dpg.draw_polyline(points=get_points_from_data(slither_data), thickness=1, color=[0, 255, 0])
372 | apple = dpg.draw_rectangle(pmin=[0, 0], pmax=[2, 2], thickness=0, color=[255, 0, 0],
373 | fill=[255, 0, 0])
374 |
375 | with dpg.child_window(autosize_x=True, autosize_y=True):
376 | with dpg.group():
377 | with dpg.child_window(height=340):
378 | dpg.add_spacer(height=5)
379 | settings_text = dpg.add_text(default_value=" Settings")
380 | dpg.bind_item_font(item=settings_text, font=bold_font)
381 | dpg.add_spacer(height=5)
382 | dpg.add_separator()
383 | dpg.add_spacer(height=5)
384 | snake_speed = dpg.add_drag_int(label="Snake speed", width=130, clamped=True, min_value=1,
385 | max_value=10, default_value=5)
386 | dpg.add_spacer(height=15)
387 | snake_color = dpg.add_color_edit(label="Snake color", default_value=[0, 255, 0], no_alpha=True,
388 | width=130, callback=change_colors)
389 | dpg.add_spacer(height=5)
390 | apple_color = dpg.add_color_edit(label="Apple color", default_value=[255, 0, 0], no_alpha=True,
391 | width=130, callback=change_colors)
392 | dpg.add_spacer(height=5)
393 | burrow_color = dpg.add_color_edit(label="Burrow color", default_value=[33, 33, 33],
394 | no_alpha=True, width=130, callback=change_colors)
395 | dpg.add_spacer(height=15)
396 | fix_snake_length = dpg.add_checkbox(label="Fix snake length", default_value=False,
397 | callback=check_snake_length)
398 | dpg.add_spacer(height=15)
399 | dpg.add_button(label="Reset Settings", width=-1, height=30, callback=reset_settings)
400 |
401 | dpg.add_separator()
402 | dpg.add_spacer()
403 | dpg.add_button(label="Start", callback=move_snakeDispatcher, width=-1, height=30)
404 | dpg.add_button(label="Restart", callback=restart_snake, width=-1, height=30)
405 | dpg.add_button(label="Reset Stats", width=-1, height=30, callback=reset_stats)
406 | dpg.add_spacer()
407 | dpg.add_separator()
408 | dpg.add_spacer()
409 | dpg.add_button(label="Help", width=-1, height=30, callback=open_help)
410 |
411 | key_release_handler_parent = dpg.add_handler_registry()
412 | dpg.add_key_release_handler(callback=key_release_handler, parent=key_release_handler_parent)
413 |
414 | place_apple()
415 |
416 | dpg.bind_theme(global_theme)
417 | dpg.bind_font(regular_font)
418 |
419 | dpg.setup_dearpygui()
420 | dpg.show_viewport()
421 | dpg.set_primary_window(window=main_window, value=True)
422 | dpg.start_dearpygui()
423 | dpg.destroy_context()
424 |
425 |
426 | initial_slither_points()
427 | main_window_setup()
428 |
--------------------------------------------------------------------------------