diff --git a/Snake Game Using Turtle/README.md b/Snake Game Using Turtle/README.md new file mode 100644 index 00000000000..2d717a602e5 --- /dev/null +++ b/Snake Game Using Turtle/README.md @@ -0,0 +1,26 @@ +# My Interactive Snake Game + +Hey there! I’m [Prashant Gohel](https://github.com/prashantgohel321) + +I took the classic Snake game and gave it a modern, interactive twist — with a sleek UI, smooth gameplay, and fun new controls. This project was all about making a nostalgic game feel fresh again! + +![alt text]() + +## What I Added + +**Fresh UI:** Clean, responsive, and almost full-screen — with a neat header for score and controls. + +**Interactive Controls**: Play, Pause, Resume, Restart — all on-screen (plus spacebar support!). + +**High Score System**: Tracks and saves your best score in highscore.txt — challenge yourself! + +**Smooth Game Flow**: Smart state system for seamless transitions between screens. + +---- + +
+ +
+💡 Built with Python
+Feel free to fork, star ⭐, or suggest improvements — I’d love to hear your thoughts! +
\ No newline at end of file diff --git a/Snake Game Using Turtle/colors.py b/Snake Game Using Turtle/colors.py new file mode 100644 index 00000000000..05fac02e5a2 --- /dev/null +++ b/Snake Game Using Turtle/colors.py @@ -0,0 +1,28 @@ +""" +This file contains the color palette for the game, now including +colors for the new interactive buttons. +""" +# A fresh and vibrant color theme +# --> food.py +FOOD_COLOR = "#C70039" # A bright, contrasting red + +# --> main.py +BG_COLOR = '#F0F8FF' # AliceBlue, a very light and clean background + +# --> scoreboard.py +GAME_OVER_COLOR = '#D21312' # Strong red for game over message +SCORE_COLOR = '#27374D' # Dark blue for high-contrast text +MESSAGE_COLOR = '#27374D' # Consistent dark blue for other messages + +# --> snake.py +FIRST_SEGMENT_COLOR = '#006400' # DarkGreen for the snake's head +BODY_COLOR = '#2E8B57' # SeaGreen for the snake's body + +# --> wall.py +WALL_COLOR = '#27374D' # Dark blue for a solid, visible border + +# --> UI Controls (Buttons) +BUTTON_BG_COLOR = "#526D82" +BUTTON_TEXT_COLOR = "#F0F8FF" +BUTTON_BORDER_COLOR = "#27374D" + diff --git a/Snake Game Using Turtle/demo (1).gif b/Snake Game Using Turtle/demo (1).gif new file mode 100644 index 00000000000..be7cff2f1f6 Binary files /dev/null and b/Snake Game Using Turtle/demo (1).gif differ diff --git a/Snake Game Using Turtle/food.py b/Snake Game Using Turtle/food.py new file mode 100644 index 00000000000..59dcd5eb740 --- /dev/null +++ b/Snake Game Using Turtle/food.py @@ -0,0 +1,27 @@ +""" +This file handles the creation of food. Its placement is now controlled +by the main game logic to ensure it spawns within the correct boundaries. +""" + +from turtle import Turtle +import random +import colors + +class Food(Turtle): + """ This class generates food for the snake to eat. """ + def __init__(self): + super().__init__() + self.shape("circle") + self.penup() + self.shapesize(stretch_len=0.7, stretch_wid=0.7) + self.color(colors.FOOD_COLOR) + self.speed("fastest") + + def refresh(self, left_wall, right_wall, bottom_wall, top_wall): + """Moves the food to a new random position within the provided game boundaries.""" + # Add a margin so food doesn't spawn exactly on the edge + margin = 20 + random_x = random.randint(int(left_wall) + margin, int(right_wall) - margin) + random_y = random.randint(int(bottom_wall) + margin, int(top_wall) - margin) + self.goto(random_x, random_y) + diff --git a/Snake Game Using Turtle/highscore.txt b/Snake Game Using Turtle/highscore.txt new file mode 100644 index 00000000000..62f9457511f --- /dev/null +++ b/Snake Game Using Turtle/highscore.txt @@ -0,0 +1 @@ +6 \ No newline at end of file diff --git a/Snake Game Using Turtle/main.py b/Snake Game Using Turtle/main.py new file mode 100644 index 00000000000..9b874f1a3df --- /dev/null +++ b/Snake Game Using Turtle/main.py @@ -0,0 +1,195 @@ +""" +This is the main file that runs the Snake game. +It handles screen setup, dynamic boundaries, UI controls (buttons), +game state management, and the main game loop. +""" +from turtle import Screen, Turtle +from snake import Snake +from food import Food +from scoreboard import Scoreboard +from wall import Wall +import colors + +# --- CONSTANTS --- +MOVE_DELAY_MS = 100 # Game speed in milliseconds + +# --- GAME STATE --- +game_state = "start" # Possible states: "start", "playing", "paused", "game_over" + +# --- SCREEN SETUP --- +screen = Screen() +screen.setup(width=0.9, height=0.9) # Set up a nearly fullscreen window +screen.bgcolor(colors.BG_COLOR) +screen.title("Interactive Snake Game") +screen.tracer(0) + +# --- DYNAMIC GAME BOUNDARIES --- +WIDTH = screen.window_width() +HEIGHT = screen.window_height() +# These boundaries are calculated to be inside the visible wall with a safe margin +LEFT_WALL = -WIDTH / 2 + 25 +RIGHT_WALL = WIDTH / 2 - 25 +TOP_WALL = HEIGHT / 2 - 85 +BOTTOM_WALL = -HEIGHT / 2 + 25 + +# --- GAME OBJECTS --- +wall = Wall() +snake = Snake() +food = Food() +# Initial food placement is now handled after boundaries are calculated +food.refresh(LEFT_WALL, RIGHT_WALL, BOTTOM_WALL, TOP_WALL) +scoreboard = Scoreboard() + +# --- UI CONTROLS (BUTTONS) --- +buttons = {} # Dictionary to hold button turtles and their properties + +def create_button(name, x, y, width=120, height=40): + """Creates a turtle-based button with a label.""" + if name in buttons and buttons[name]['turtle'] is not None: + buttons[name]['turtle'].clear() + + button_turtle = Turtle() + button_turtle.hideturtle() + button_turtle.penup() + button_turtle.speed("fastest") + + button_turtle.goto(x - width/2, y - height/2) + button_turtle.color(colors.BUTTON_BORDER_COLOR, colors.BUTTON_BG_COLOR) + button_turtle.begin_fill() + for _ in range(2): + button_turtle.forward(width) + button_turtle.left(90) + button_turtle.forward(height) + button_turtle.left(90) + button_turtle.end_fill() + + button_turtle.goto(x, y - 12) + button_turtle.color(colors.BUTTON_TEXT_COLOR) + button_turtle.write(name, align="center", font=("Lucida Sans", 14, "bold")) + + buttons[name] = {'turtle': button_turtle, 'x': x, 'y': y, 'w': width, 'h': height, 'visible': True} + +def hide_button(name): + """Hides a button by clearing its turtle.""" + if name in buttons and buttons[name]['visible']: + buttons[name]['turtle'].clear() + buttons[name]['visible'] = False + +def manage_buttons(): + """Shows or hides buttons based on the current game state.""" + all_buttons = ["Play", "Pause", "Resume", "Restart"] + for btn_name in all_buttons: + hide_button(btn_name) + + btn_x = WIDTH / 2 - 100 + btn_y = HEIGHT / 2 - 45 + + if game_state == "start": + create_button("Play", 0, -100) + elif game_state == "playing": + create_button("Pause", btn_x, btn_y) + elif game_state == "paused": + create_button("Resume", btn_x, btn_y) + elif game_state == "game_over": + create_button("Restart", btn_x, btn_y) + +# --- GAME LOGIC & STATE TRANSITIONS --- +def start_game(): + global game_state + if game_state == "start": + game_state = "playing" + scoreboard.update_scoreboard() + +def toggle_pause_resume(): + global game_state + if game_state == "playing": + game_state = "paused" + scoreboard.display_pause() + elif game_state == "paused": + game_state = "playing" + scoreboard.update_scoreboard() + +def restart_game(): + global game_state + if game_state == "game_over": + game_state = "playing" + snake.reset() + food.refresh(LEFT_WALL, RIGHT_WALL, BOTTOM_WALL, TOP_WALL) + scoreboard.reset() + +def is_click_on_button(name, x, y): + """Checks if a click (x, y) is within the bounds of a visible button.""" + if name in buttons and buttons[name]['visible']: + btn = buttons[name] + return (btn['x'] - btn['w']/2 < x < btn['x'] + btn['w']/2 and + btn['y'] - btn['h']/2 < y < btn['y'] + btn['h']/2) + return False + +def handle_click(x, y): + """Main click handler to delegate actions based on button clicks.""" + if game_state == "start" and is_click_on_button("Play", x, y): + start_game() + elif game_state == "playing" and is_click_on_button("Pause", x, y): + toggle_pause_resume() + elif game_state == "paused" and is_click_on_button("Resume", x, y): + toggle_pause_resume() + elif game_state == "game_over" and is_click_on_button("Restart", x, y): + restart_game() + +# --- KEYBOARD HANDLERS --- +def handle_snake_up(): + if game_state in ["start", "playing"]: + start_game() + snake.up() +def handle_snake_down(): + if game_state in ["start", "playing"]: + start_game() + snake.down() +def handle_snake_left(): + if game_state in ["start", "playing"]: + start_game() + snake.left() +def handle_snake_right(): + if game_state in ["start", "playing"]: + start_game() + snake.right() + +# --- KEY & MOUSE BINDINGS --- +screen.listen() +screen.onkey(handle_snake_up, "Up") +screen.onkey(handle_snake_down, "Down") +screen.onkey(handle_snake_left, "Left") +screen.onkey(handle_snake_right, "Right") +screen.onkey(toggle_pause_resume, "space") +screen.onkey(restart_game, "r") +screen.onkey(restart_game, "R") +screen.onclick(handle_click) + +# --- MAIN GAME LOOP --- +def game_loop(): + global game_state + if game_state == "playing": + snake.move() + # Collision with food + if snake.head.distance(food) < 20: + food.refresh(LEFT_WALL, RIGHT_WALL, BOTTOM_WALL, TOP_WALL) + snake.extend() + scoreboard.increase_score() + # Collision with wall + if not (LEFT_WALL < snake.head.xcor() < RIGHT_WALL and BOTTOM_WALL < snake.head.ycor() < TOP_WALL): + game_state = "game_over" + scoreboard.game_over() + # Collision with tail + for segment in snake.segments[1:]: + if snake.head.distance(segment) < 10: + game_state = "game_over" + scoreboard.game_over() + manage_buttons() + screen.update() + screen.ontimer(game_loop, MOVE_DELAY_MS) + +# --- INITIALIZE GAME --- +scoreboard.display_start_message() +game_loop() +screen.exitonclick() + diff --git a/Snake Game Using Turtle/scoreboard.py b/Snake Game Using Turtle/scoreboard.py new file mode 100644 index 00000000000..4ca9265071c --- /dev/null +++ b/Snake Game Using Turtle/scoreboard.py @@ -0,0 +1,80 @@ +""" +This file manages the display of the score, high score, and game messages. +It now positions the score dynamically in the top-left corner. +""" +from turtle import Turtle, Screen +import colors + +# Constants for styling and alignment +ALIGNMENT = "left" +SCORE_FONT = ("Lucida Sans", 20, "bold") +MESSAGE_FONT = ("Courier", 40, "bold") +INSTRUCTION_FONT = ("Lucida Sans", 16, "normal") + +class Scoreboard(Turtle): + """ This class maintains the scoreboard, high score, and game messages. """ + def __init__(self): + super().__init__() + self.screen = Screen() # Get access to the screen object + self.score = 0 + self.high_score = self.load_high_score() + self.penup() + self.hideturtle() + self.update_scoreboard() + + def load_high_score(self): + """Loads high score from highscore.txt. Returns 0 if not found.""" + try: + with open("highscore.txt", mode="r") as file: + return int(file.read()) + except (FileNotFoundError, ValueError): + return 0 + + def update_scoreboard(self): + """Clears and rewrites the score and high score in the top-left corner.""" + self.clear() + self.color(colors.SCORE_COLOR) + # Dynamically calculate position to be well-placed in the header + x_pos = -self.screen.window_width() / 2 + 30 + y_pos = self.screen.window_height() / 2 - 60 + self.goto(x_pos, y_pos) + self.write(f"Score: {self.score} | High Score: {self.high_score}", align=ALIGNMENT, font=SCORE_FONT) + + def increase_score(self): + """Increases score and updates the display.""" + self.score += 1 + self.update_scoreboard() + + def reset(self): + """Checks for new high score, saves it, and resets the score.""" + if self.score > self.high_score: + self.high_score = self.score + with open("highscore.txt", mode="w") as file: + file.write(str(self.high_score)) + self.score = 0 + self.update_scoreboard() + + def game_over(self): + """Displays the Game Over message and instructions.""" + self.goto(0, 40) + self.color(colors.GAME_OVER_COLOR) + self.write("GAME OVER", align="center", font=MESSAGE_FONT) + self.goto(0, -40) + self.write("Click 'Restart' or Press 'R'", align="center", font=INSTRUCTION_FONT) + + def display_pause(self): + """Displays the PAUSED message.""" + self.goto(0, 40) + self.color(colors.MESSAGE_COLOR) + self.write("PAUSED", align="center", font=MESSAGE_FONT) + self.goto(0, -40) + self.write("Click 'Resume' or Press 'Space'", align="center", font=INSTRUCTION_FONT) + + def display_start_message(self): + """Displays the welcome message and starting instructions.""" + self.goto(0, 40) + self.color(colors.MESSAGE_COLOR) + self.write("SNAKE GAME", align="center", font=MESSAGE_FONT) + self.goto(0, -40) + self.write("Click 'Play' or an Arrow Key to Start", align="center", font=INSTRUCTION_FONT) + diff --git a/Snake Game Using Turtle/screenshots b/Snake Game Using Turtle/screenshots new file mode 100644 index 00000000000..d3f5a12faa9 --- /dev/null +++ b/Snake Game Using Turtle/screenshots @@ -0,0 +1 @@ + diff --git a/Snake Game Using Turtle/snake.py b/Snake Game Using Turtle/snake.py new file mode 100644 index 00000000000..e9fb153c317 --- /dev/null +++ b/Snake Game Using Turtle/snake.py @@ -0,0 +1,73 @@ +""" +This file is responsible for creating the snake and managing its movement, +extension, and reset functionality. +""" +from turtle import Turtle +import colors + +STARTING_POSITIONS = [(0, 0), (-20, 0), (-40, 0)] +MOVE_DISTANCE = 20 +UP, DOWN, LEFT, RIGHT = 90, 270, 180, 0 + +class Snake: + """ This class creates a snake body and contains methods for movement and extension. """ + def __init__(self): + self.segments = [] + self.create_snake() + self.head = self.segments[0] + + def create_snake(self): + """ Creates the initial snake body. """ + for position in STARTING_POSITIONS: + self.add_segment(position) + self.segments[0].color(colors.FIRST_SEGMENT_COLOR) + + def add_segment(self, position): + """ Adds a new segment to the snake. """ + new_segment = Turtle(shape="square") + new_segment.penup() + new_segment.goto(position) + new_segment.color(colors.BODY_COLOR) + self.segments.append(new_segment) + + def extend(self): + """ Adds a new segment to the snake's tail. """ + self.add_segment(self.segments[-1].position()) + self.segments[0].color(colors.FIRST_SEGMENT_COLOR) + + def move(self): + """ Moves the snake forward by moving each segment to the position of the one in front.""" + for i in range(len(self.segments) - 1, 0, -1): + x = self.segments[i - 1].xcor() + y = self.segments[i - 1].ycor() + self.segments[i].goto(x, y) + self.head.forward(MOVE_DISTANCE) + + def reset(self): + """Hides the old snake and creates a new one for restarting the game.""" + for segment in self.segments: + segment.hideturtle() + self.segments.clear() + self.create_snake() + self.head = self.segments[0] + + def up(self): + """Turns the snake's head upwards, preventing it from reversing.""" + if self.head.heading() != DOWN: + self.head.setheading(UP) + + def down(self): + """Turns the snake's head downwards, preventing it from reversing.""" + if self.head.heading() != UP: + self.head.setheading(DOWN) + + def left(self): + """Turns the snake's head to the left, preventing it from reversing.""" + if self.head.heading() != RIGHT: + self.head.setheading(LEFT) + + def right(self): + """Turns the snake's head to the right, preventing it from reversing.""" + if self.head.heading() != LEFT: + self.head.setheading(RIGHT) + diff --git a/Snake Game Using Turtle/wall.py b/Snake Game Using Turtle/wall.py new file mode 100644 index 00000000000..dc47848961b --- /dev/null +++ b/Snake Game Using Turtle/wall.py @@ -0,0 +1,46 @@ +"""This file creates a responsive boundary wall that adapts to the game window size.""" + +from turtle import Turtle, Screen +import colors + +class Wall: + """ This class creates a wall around the game screen that adjusts to its dimensions. """ + def __init__(self): + self.screen = Screen() + self.create_wall() + + def create_wall(self): + """Draws a responsive game border and a header area for the scoreboard and controls.""" + width = self.screen.window_width() + height = self.screen.window_height() + + # Calculate coordinates for the border based on screen size + top = height / 2 + bottom = -height / 2 + left = -width / 2 + right = width / 2 + + wall = Turtle() + wall.hideturtle() + wall.speed("fastest") + wall.color(colors.WALL_COLOR) + wall.penup() + + # Draw the main rectangular border + wall.goto(left + 10, top - 10) + wall.pendown() + wall.pensize(10) + wall.goto(right - 10, top - 10) + wall.goto(right - 10, bottom + 10) + wall.goto(left + 10, bottom + 10) + wall.goto(left + 10, top - 10) + + # Draw a line to create a separate header section for the score and buttons + wall.penup() + wall.goto(left + 10, top - 70) + wall.pendown() + wall.pensize(5) + wall.goto(right - 10, top - 70) + + self.screen.update() +