diff --git a/games/Snake-Game/Snake-Game.py b/games/Snake-Game/Snake-Game.py index 20ad8e7..ec2addc 100644 --- a/games/Snake-Game/Snake-Game.py +++ b/games/Snake-Game/Snake-Game.py @@ -1,6 +1,13 @@ import turtle import random import time +import pygame + +# ================= SOUND ================= + +pygame.mixer.init() +eat_sound = pygame.mixer.Sound("sounds/eat.wav") +gameover_sound = pygame.mixer.Sound("sounds/gameover.wav") # ================= SCREEN SETUP ================= @@ -14,7 +21,6 @@ border = turtle.Turtle() border.hideturtle() -border.speed(0) border.color("#00FFAA") border.pensize(4) border.penup() @@ -32,56 +38,31 @@ title_text.color("#00FFAA") title_text.penup() title_text.goto(0, 330) +title_text.write("SNAKE GAME", align="center", font=("Arial", 28, "bold")) -title_text.write( - "SNAKE GAME", - align="center", - font=("Arial", 28, "bold") -) - -# ================= INSTRUCTIONS ================= +# ================= TEXT ================= -instruction = turtle.Turtle() -instruction.hideturtle() -instruction.color("white") -instruction.penup() -instruction.goto(0, -340) - -instruction.write( - "Use Arrow Keys To Move", - align="center", - font=("Arial", 14, "normal") -) +game_text = turtle.Turtle() +game_text.hideturtle() +game_text.color("white") +game_text.penup() +game_text.goto(0, 0) # ================= GAME CONSTANTS ================= GRID_SIZE = 15 -GRID_RANGE = 18 # Number of steps from center to border +GRID_RANGE = 18 BORDER_LIMIT = 280 -# ================= SNAKE HEAD ================= +# ================= SNAKE ================= head = turtle.Turtle() head.shape("circle") head.color("#00FF66") -head.shapesize(1.3, 1.3) head.penup() -head.goto(0, 0) head.direction = "stop" -# ================= EYES ================= - -left_eye = turtle.Turtle() -left_eye.shape("circle") -left_eye.color("black") -left_eye.shapesize(0.25, 0.25) -left_eye.penup() - -right_eye = turtle.Turtle() -right_eye.shape("circle") -right_eye.color("black") -right_eye.shapesize(0.25, 0.25) -right_eye.penup() +parts = [] # ================= FOOD ================= @@ -96,121 +77,84 @@ current_food = None -# ================= BODY PARTS ================= +def generate_food(): + global current_food -parts = [] + current_food = random.choice(food_types) + food.shape(current_food["shape"]) + food.color(current_food["color"]) + food.shapesize(current_food["size"]) -green_shades = [ - "#66FF99", - "#55EE88", - "#44DD77", - "#33CC66", - "#22BB55" -] + while True: + x = random.randint(-GRID_RANGE, GRID_RANGE) * GRID_SIZE + y = random.randint(-GRID_RANGE, GRID_RANGE) * GRID_SIZE + + occupied = False + + if (x, y) == (int(head.xcor()), int(head.ycor())): + occupied = True + + for p in parts: + if (int(p.xcor()), int(p.ycor())) == (x, y): + occupied = True + break + + if not occupied: + food.goto(x, y) + return + +generate_food() # ================= SCORE ================= score = 0 high_score = 0 +level = 1 +speed = 0.05 score_text = turtle.Turtle() score_text.hideturtle() -score_text.speed(0) score_text.color("white") score_text.penup() score_text.goto(0, 295) -# ================= GAME OVER TEXT ================= - -game_text = turtle.Turtle() -game_text.hideturtle() -game_text.color("red") -game_text.penup() -game_text.goto(0, 0) - -# ================= EFFECT TEXT ================= - -effect_text = turtle.Turtle() -effect_text.hideturtle() -effect_text.color("#FFD700") -effect_text.penup() - -# ================= UPDATE SCORE ================= - def update_score(): - score_text.clear() - score_text.write( - f"SCORE: {score} HIGH SCORE: {high_score}", + f"SCORE: {score} LEVEL: {level} HIGH SCORE: {high_score}", align="center", - font=("Courier New", 18, "bold") + font=("Courier New", 16, "bold") ) update_score() -# ================= GENERATE FOOD ================= - -def generate_food(): +# ================= PAUSE ================= - global current_food +paused = False - current_food = random.choice(food_types) +def toggle_pause(): + global paused + paused = not paused - food.shape(current_food["shape"]) - food.color(current_food["color"]) - food.shapesize(current_food["size"]) - - max_attempts = 100 - attempt = 0 - - while attempt < max_attempts: - x = random.randint(-GRID_RANGE, GRID_RANGE) * GRID_SIZE - y = random.randint(-GRID_RANGE, GRID_RANGE) * GRID_SIZE - - # Check if the coordinate is occupied by the snake's head - if head.distance(x, y) < GRID_SIZE: - attempt += 1 - continue - - # Check if the coordinate is occupied by any body part - occupied = False - for part in parts: - if part.distance(x, y) < GRID_SIZE: - occupied = True - break - - if not occupied: - food.goto(x, y) - return - - attempt += 1 - - # Fallback: If no space found after max attempts, game is likely won or grid is full - # Reset game or show win message (for now, we'll reset to avoid freeze) - reset_game() - -generate_food() - -# ================= FOOD EFFECT ================= - -def food_effect(x, y, points): - - effect_text.goto(x, y + 20) + game_text.clear() - effect_text.clear() + if paused: + game_text.write("PAUSED", align="center", font=("Arial", 24, "bold")) - effect_text.write( - f"+{points}", - align="center", - font=("Arial", 16, "bold") - ) +screen.listen() +screen.onkeypress(toggle_pause, "p") - screen.update() +# ================= COUNTDOWN ================= - time.sleep(0.2) +def countdown(): + for text in ["3", "2", "1", "GO!"]: + game_text.clear() + game_text.write(text, align="center", font=("Arial", 30, "bold")) + screen.update() + time.sleep(1) + game_text.clear() - effect_text.clear() +countdown() # ================= CONTROLS ================= @@ -230,116 +174,55 @@ def move_right(): if head.direction != "left": head.direction = "right" -screen.listen() - screen.onkeypress(move_up, "Up") screen.onkeypress(move_down, "Down") screen.onkeypress(move_left, "Left") screen.onkeypress(move_right, "Right") -# ================= MOVE FUNCTION ================= +# ================= MOVE ================= def move(): - if head.direction == "up": head.sety(head.ycor() + GRID_SIZE) - - if head.direction == "down": - head.sety(head.ycor() - GRID_SIZE) - - if head.direction == "left": - head.setx(head.xcor() - GRID_SIZE) - - if head.direction == "right": - head.setx(head.xcor() + GRID_SIZE) - -# ================= UPDATE EYES ================= - -def update_eyes(): - - x = head.xcor() - y = head.ycor() - - if head.direction == "up": - left_eye.goto(x - 5, y + 8) - right_eye.goto(x + 5, y + 8) - elif head.direction == "down": - left_eye.goto(x - 5, y - 8) - right_eye.goto(x + 5, y - 8) - + head.sety(head.ycor() - GRID_SIZE) elif head.direction == "left": - left_eye.goto(x - 8, y + 5) - right_eye.goto(x - 8, y - 5) - + head.setx(head.xcor() - GRID_SIZE) elif head.direction == "right": - left_eye.goto(x + 8, y + 5) - right_eye.goto(x + 8, y - 5) - - else: - left_eye.goto(x - 5, y + 8) - right_eye.goto(x + 5, y + 8) + head.setx(head.xcor() + GRID_SIZE) -# ================= RESET GAME ================= +# ================= RESET ================= def reset_game(): + global score, level, speed - global score + gameover_sound.play() time.sleep(1) head.goto(0, 0) head.direction = "stop" - left_eye.goto(-1000, -1000) - right_eye.goto(-1000, -1000) - for p in parts: p.goto(1000, 1000) - parts.clear() - game_text.clear() - - game_text.write( - "GAME OVER\nPress Any Arrow Key To Restart", - align="center", - font=("Arial", 24, "bold") - ) - - screen.update() - - time.sleep(1.5) - - game_text.clear() - score = 0 + level = 1 + speed = 0.05 update_score() -# ================= MAIN GAME LOOP ================= - -pulse_size = 1.2 -pulse_direction = 0.03 +# ================= MAIN LOOP ================= while True: - # FOOD ANIMATION - - pulse_size += pulse_direction - - if pulse_size > 1.4: - pulse_direction = -0.03 - - if pulse_size < 1.1: - pulse_direction = 0.03 - - food.shapesize(pulse_size) - - food.setheading(food.heading() + 5) + screen.update() - # BORDER COLLISION + if paused: + continue + # Border collision if ( head.xcor() > BORDER_LIMIT or head.xcor() < -BORDER_LIMIT or @@ -348,70 +231,52 @@ def reset_game(): ): reset_game() - # FOOD COLLISION - + # Food collision if head.distance(food) < GRID_SIZE: - food_effect( - food.xcor(), - food.ycor(), - current_food["points"] - ) + eat_sound.play() - generate_food() + score += current_food["points"] - # NEW BODY PART + if score > high_score: + high_score = score - new_part = turtle.Turtle() + # LEVEL SYSTEM + if score % 5 == 0: + level += 1 + speed -= 0.005 + game_text.clear() + game_text.write(f"LEVEL {level}", align="center", font=("Arial", 24, "bold")) + screen.update() + time.sleep(0.5) + game_text.clear() + + generate_food() + + new_part = turtle.Turtle() new_part.shape("circle") - new_part.color(random.choice(green_shades)) - new_part.shapesize(1.0, 1.0) + new_part.color("#66FF99") new_part.penup() parts.append(new_part) - # SNAKE COLOR CHANGE - - head.color(random.choice(green_shades)) - - # SCORE UPDATE - - score += current_food["points"] - - if score > high_score: - high_score = score - update_score() - # MOVE BODY - + # Move body for i in range(len(parts) - 1, 0, -1): - x = parts[i - 1].xcor() y = parts[i - 1].ycor() - parts[i].goto(x, y) - if len(parts) > 0: + if parts: parts[0].goto(head.xcor(), head.ycor()) - # MOVE HEAD - move() - # UPDATE EYES - - update_eyes() - - # SELF COLLISION - + # Self collision for p in parts: - if p.distance(head) < 12: - reset_game() - screen.update() - - time.sleep(0.05) \ No newline at end of file + time.sleep(speed) diff --git a/games/Snake-Game/food.py b/games/Snake-Game/food.py index d039b39..26132af 100644 --- a/games/Snake-Game/food.py +++ b/games/Snake-Game/food.py @@ -14,22 +14,21 @@ def __init__(self): def reposition(self, snake: Snake): attempts = 0 + + # Get all snake positions (aligned to grid) + snake_positions = [(int(p.xcor()), int(p.ycor())) for p in snake.parts] + snake_positions.append((int(snake.head.xcor()), int(snake.head.ycor()))) + while attempts < 100: x = random.randint(-14, 14) * 20 y = random.randint(-14, 14) * 20 - overlaps = False - - if snake.head.distance(x, y) < COLLISION_DISTANCE: - overlaps = True - for p in snake.parts: - if p.distance(x, y) < COLLISION_DISTANCE: - overlaps = True - break - - if not overlaps: + + # Check exact match instead of distance + if (x, y) not in snake_positions: self.item.goto(x, y) - break + return + attempts += 1 - if attempts >= 100: - self.item.goto(2000, 2000) + # fallback (rare case) + self.item.goto(2000, 2000) diff --git a/games/Snake-Game/main.py b/games/Snake-Game/main.py index 3d34381..1c449bf 100644 --- a/games/Snake-Game/main.py +++ b/games/Snake-Game/main.py @@ -1,3 +1,4 @@ +import pygame import turtle import time from constants import SCREEN_WIDTH, SCREEN_HEIGHT, COLLISION_DISTANCE, SLEEP_TIME @@ -5,8 +6,15 @@ from food import Food from scoreboard import Scoreboard + class SnakeGame: def __init__(self): + # Game state + self.paused = False + self.delay = SLEEP_TIME + self.level = 1 + + # Screen setup self.screen = turtle.Screen() self.screen.title("Snake Game (Modular OOP)") self.screen.bgcolor("black") @@ -15,16 +23,23 @@ def __init__(self): self._draw_borders() + # Game objects self.snake = Snake() self.food = Food() self.scoreboard = Scoreboard() + # Sound setup + pygame.mixer.init() + self.eat_sound = pygame.mixer.Sound("sounds/Apple_Eating.mp3") + self.gameover_sound = pygame.mixer.Sound("sounds/Game_over.mp3") + # Keyboard bindings self.screen.listen() self.screen.onkeypress(self.snake.move_up, "Up") self.screen.onkeypress(self.snake.move_down, "Down") self.screen.onkeypress(self.snake.move_left, "Left") self.screen.onkeypress(self.snake.move_right, "Right") + self.screen.onkeypress(self.toggle_pause, "p") # Pause key def _draw_borders(self): box = turtle.Turtle() @@ -38,28 +53,75 @@ def _draw_borders(self): box.forward(600) box.right(90) + # Countdown before game starts + def countdown(self): + for text in ["3", "2", "1", "GO!"]: + self.scoreboard.show_message(text) + self.screen.update() + time.sleep(1) + + self.scoreboard.update_display() + + # Pause toggle + def toggle_pause(self): + self.paused = not self.paused + + if self.paused: + self.scoreboard.show_message("PAUSED") + else: + self.scoreboard.update_display() + def run(self): + self.countdown() # Start countdown + while True: self.screen.update() + # Pause check + if self.paused: + continue + + # Boundary collision if self.snake.check_boundary_collision(): + self.gameover_sound.play() self.snake.reset() self.scoreboard.reset() + self.delay = SLEEP_TIME + self.level = 1 - # Check food collision + # Food collision if self.snake.head.distance(self.food.item) < COLLISION_DISTANCE: self.food.reposition(self.snake) self.snake.add_part() + self.scoreboard.increase() + self.eat_sound.play() + # Level system + if self.scoreboard.score % 5 == 0: + self.level += 1 + self.delay -= 0.01 + + # Optional level message + self.scoreboard.show_message(f"LEVEL {self.level}") + self.screen.update() + time.sleep(0.5) + self.scoreboard.update_display() + + # Move snake self.snake.move() - # Check body collision + # Self collision if self.snake.check_self_collision(): + self.gameover_sound.play() self.snake.reset() self.scoreboard.reset() + self.delay = SLEEP_TIME + self.level = 1 + + # Game speed + time.sleep(self.delay) - time.sleep(SLEEP_TIME) if __name__ == "__main__": game = SnakeGame() diff --git a/games/Snake-Game/scoreboard.py b/games/Snake-Game/scoreboard.py index 8633094..9be90a5 100644 --- a/games/Snake-Game/scoreboard.py +++ b/games/Snake-Game/scoreboard.py @@ -1,27 +1,45 @@ import turtle - class Scoreboard: def __init__(self): self.score = 0 + self.level = 1 + self.high_score = 0 + self.display = turtle.Turtle() self.display.hideturtle() self.display.color("white") self.display.penup() self.display.goto(0, 320) + self.update_display() def update_display(self): self.display.clear() self.display.write( - f"Score: {self.score}", - align="center", + f"Score: {self.score} | Level: {self.level} | High Score: {self.high_score}", + align="center", font=("Arial", 18, "normal") ) def increase(self): self.score += 1 + + # Update high score + if self.score > self.high_score: + self.high_score = self.score + + self.update_display() + + def level_up(self): + self.level += 1 self.update_display() def reset(self): self.score = 0 + self.level = 1 self.update_display() + + def show_message(self, text): + self.display.goto(0, 0) + self.display.write(text, align="center", font=("Arial", 30, "bold")) + self.display.goto(0, 320) diff --git a/games/Snake-Game/snake.py b/games/Snake-Game/snake.py index 3b4ca9e..b8f116e 100644 --- a/games/Snake-Game/snake.py +++ b/games/Snake-Game/snake.py @@ -33,23 +33,32 @@ def move(self): y = self.parts[i - 1].ycor() self.parts[i].goto(x, y) - if len(self.parts) > 0: + if self.parts: self.parts[0].goto(self.head.xcor(), self.head.ycor()) - if self.head.direction == "up": - self.head.sety(self.head.ycor() + MOVE_DISTANCE) - if self.head.direction == "down": - self.head.sety(self.head.ycor() - MOVE_DISTANCE) - if self.head.direction == "left": - self.head.setx(self.head.xcor() - MOVE_DISTANCE) - if self.head.direction == "right": - self.head.setx(self.head.xcor() + MOVE_DISTANCE) + moves = { + "up": (0, MOVE_DISTANCE), + "down": (0, -MOVE_DISTANCE), + "left": (-MOVE_DISTANCE, 0), + "right": (MOVE_DISTANCE, 0) + } + + if self.head.direction in moves: + dx, dy = moves[self.head.direction] + self.head.goto(self.head.xcor() + dx, self.head.ycor() + dy) def add_part(self): new_part = turtle.Turtle() new_part.shape("square") new_part.color("lightgreen") new_part.penup() + + if self.parts: + last_part = self.parts[-1] + new_part.goto(last_part.xcor(), last_part.ycor()) + else: + new_part.goto(self.head.xcor(), self.head.ycor()) + self.parts.append(new_part) def check_boundary_collision(self) -> bool: @@ -61,15 +70,21 @@ def check_boundary_collision(self) -> bool: ) def check_self_collision(self) -> bool: - for p in self.parts: - if p.distance(self.head) < BODY_COLLISION_DISTANCE: + head_pos = (int(self.head.xcor()), int(self.head.ycor())) + + for p in self.parts[1:]: # It will skip first segment + if (int(p.xcor()), int(p.ycor())) == head_pos: return True return False def reset(self): - time.sleep(1) self.head.goto(0, 0) self.head.direction = "stop" + for p in self.parts: p.goto(1000, 1000) + self.parts.clear() + + def is_moving(self): + return self.head.direction != "stop" diff --git a/games/Snake-Game/sounds/Apple_Eating.mp3 b/games/Snake-Game/sounds/Apple_Eating.mp3 new file mode 100644 index 0000000..08df80a Binary files /dev/null and b/games/Snake-Game/sounds/Apple_Eating.mp3 differ diff --git a/games/Snake-Game/sounds/Game_over.mp3 b/games/Snake-Game/sounds/Game_over.mp3 new file mode 100644 index 0000000..7a268ed Binary files /dev/null and b/games/Snake-Game/sounds/Game_over.mp3 differ diff --git a/games/Snake-Game/sounds/ex b/games/Snake-Game/sounds/ex new file mode 100644 index 0000000..5ac431b --- /dev/null +++ b/games/Snake-Game/sounds/ex @@ -0,0 +1 @@ +Sound Files are here in this directory.