├── 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 | ![A demonstration of the snake game](resources/demo.gif) 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 | --------------------------------------------------------------------------------