2

I am trying to make a snake game in python and I would like the segments of the snake to flow when the user presses the WASD keys rather than the segments snapping to the user's desired direction

import pygame
import random
import time
pygame.init()
win = pygame.display.set_mode((800,600))
pygame.display.set_caption("Pygame")
clock = pygame.time.Clock()
x = 30
y = 30
x2 = x
y2 = random.randrange(1,601-30)
vel = 2
run = True
facing = 0
direction = 0
text = pygame.font.SysFont('Times New Roman',30)
score = 0
segments = []
green = ((0,128,0))
white = ((255,255,255))
counting = 0
segmentTime = time.time()
class segmentClass():
    def __init__(self,x,y,pos,color):
        self.x = x
        self.y = y
        self.pos = pos
        self.color = color
    def draw(self,win):
        pygame.draw.rect(win,(self.color),(self.x,self.y,30,30))
def gameOver():
    global run
    run = False
def segmentGrowth():
    global x2
    global y2
    global score
    global vel
    global ammount
    segments.append(segmentClass(x,y,len(segments)+1,green))
    ammount = 0
    x2 = random.randrange(1,801-30)
    y2 = random.randrange(1,601-30)
    score += 1
    print(vel)
while run:
    currentTime = time.time()
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
    vel += (score*0.0001)
    keys = pygame.key.get_pressed()
    if keys[pygame.K_w]:
        if direction != 1:
            direction = 1
            facing = -1
    if keys[pygame.K_s]:
        if direction != 1:
            direction = 1
            facing = 1
    if keys[pygame.K_a]:
        if direction != 0:
            direction = 0
            facing = -1
    if keys[pygame.K_d]:
        if direction != 0:
            direction = 0
            facing = 1
    if direction == 1:
        y += (vel*facing)
    else:
        x += (vel*facing)
    if x > x2 and x < x2 + 30 or x + 30 > x2 and x + 30 < x2 + 30:
        if y == y2:
            segmentGrowth()
        if y > y2 and y < y2 + 30 or y + 30 > y2 and y + 30 < y2 + 30:
            segmentGrowth()          
    if y > y2 and y < y2 + 30 or y + 30 > y2 and y + 30 < y2 + 30:
        if x == x2:
            segmentGrowth()
        if x > x2 and x < x2 + 30 or x + 30 > x2 and x + 30 < x2 + 30:
            segmentGrowth()
    if x > 800-30 or y > 600-30 or x < 0 or y < 0:
        gameOver()
    win.fill((0,0,0))
    for segment in segments:
        if direction == 0: #X value
            if facing == 1: #Right
                segment.x = x - (35 * segment.pos)
                segment.y = y
            else: #Left
                segment.x = x + (35 * segment.pos)
                segment.y = y
        else: #Y value
            if facing == -1: #Up
                segment.y = y + (35 * segment.pos)
                segment.x = x 
            else:#Down
                segment.y = y - (35 * segment.pos)
                segment.x = x
    for segment in segments:
        segment.draw(win)
    scoreDisplay = text.render(str(score),1,(255,255,255))
    win.blit(scoreDisplay,(760,0))
    pygame.draw.rect(win,(0,128,0),(x,y,30,30))
    pygame.draw.rect(win,(255,0,0),(x2,y2,30,30))
    pygame.display.update()
pygame.quit()

How it works is there is a list of segments and a class for information of each segment (ie x, y, etc). I append to that list an instance of the segment class whenever the user has collided with the red cube. I have this code:

for segment in segments:
        if direction == 0: #X value
            if facing == 1: #Right
                segment.x = x - (35 * segment.pos)
                segment.y = y
            else: #Left
                segment.x = x + (35 * segment.pos)
                segment.y = y
        else: #Y value
            if facing == -1: #Up
                segment.y = y + (35 * segment.pos)
                segment.x = x 
            else:#Down
                segment.y = y - (35 * segment.pos)
                segment.x = x

That will move all segments of the snake all at once when the player decides what direction they want the snake to move. However, the segments are snapping immediately to the x position of the head rather than moving one at a time, smoothly. If someone could help me out with this that would be great. Thanks!

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Hydra
  • 373
  • 3
  • 18

1 Answers1

2

Nice game. I recommend to create a list of points, which is a list of tuples of the snakes head positions ((x, y)). Add every position to the list:

pts = []
while run:

    # [...]

    pts.append((x, y))

Create a function which calculates the position of a part of the snake by its index (i) counted to the head of the snake. The distance to the head has to be lenToI = i * 35.
The distance between to points can be calculated by the Euclidean distance (math.sqrt((px-pnx)*(px-pnx) + (py-pny)*(py-pny)), where the points are (px, py) and (pnx, pny). If the sum of the distances between the points (lenAct) exceeds the length to point (lenToI), then the position of part i is found:

def getPos(i):
    global pts
    lenToI = i * 35
    lenAct = 0
    px, py = pts[-1]
    for j in reversed(range(len(pts)-1)):
        px, py = pts[j]
        pnx, pny = pts[j+1]
        lenAct += math.sqrt((px-pnx)*(px-pnx) + (py-pny)*(py-pny))
        if lenAct >= lenToI:
            break
    return (px, py)

Write another function cutPts, which deletes the points from the list, which ar not further required:

def cutPts(i):
    global pts
    lenToI = i * 35
    lenAct = 0
    cut_i = 0
    px, py = pts[0]
    for j in reversed(range(len(pts)-1)):
        px, py = pts[j]
        pnx, pny = pts[j+1]
        lenAct += math.sqrt((px-pnx)*(px-pnx) + (py-pny)*(py-pny))
        if lenAct >= lenToI:
            break
        cut_i = j
    del pts[:cut_i]

Update the positions of the segments in a loop:

pts.append((x, y))
for i in range(len(segments)):
    segments[i].x, segments[i].y = getPos(len(segments)-i)  
cutPts(len(segments)+1)


Regarding the comment:

how would I go about calling the gameOver() function if the head of the snake touches any of its segments? I tried using an if statement to check for collision (the same way I did for the apple) using segment.x and segment.y but this won't work since the second segment of the snake always overlaps the head when the snake moves.

Note, the head can never "touch" the first segment, except the direction is changed to the reverse direction, but this case can be handled by an extra test, with ease.
It is sufficient to check if the head "hits" any segment except the fist one which connects to the head.
Use pygame.Rect.colliderect to check for the intersection of rectangular segments:

def selfCollide():    
    for i in range(len(segments)-1):
        s = segments[i]
        if pygame.Rect(x, y, 30, 30).colliderect(pygame.Rect(s.x, s.y, 30, 30)):
            return True
    return False
if selfCollide():
    gameOver()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • Now that I have this working how would I go about calling the gameOver() function if the head of the snake touches any of its segments? I tried using an if statement to check for collision (the same way I did for the apple) using segment.x and segment.y but this won't work since the second segment of the snake always overlaps the head when the snake moves. – Hydra Jan 20 '19 at 16:52
  • @Hydra Note, the head can never "touch" the first segment, except the direction is changed to the reverse direction, but this case can be handled with an extra test. It is sufficient to check if the head "hits" any segment except the fist one which connects to the head. See the extension to the answer. – Rabbid76 Jan 20 '19 at 17:44