Home > Enterprise >  Unable to create two instances of a class (using pygame, trying to make snake game with two snakes)
Unable to create two instances of a class (using pygame, trying to make snake game with two snakes)

Time:03-22

I have created a class which defines and creates a snake using pygame. When I make one instance of the snake class, one snake will be correctly created, but when I try to make two instances, my code will not make the second snake. I'm not sure if this is because I used pygame wrongly, or because I'm doing something wrong in the classes.

Here's my code; it's a little bit long, but I've included it all because I don't know where the problem is. I'm quite sure that I'm handling all the variables and arguments correctly, and handling the keypresses is correct as well. If you copy and paste this code into the interpreter, it works fine if you say you want one snake, but doesn't work when you want to create two snakes.

import os
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"; import pygame
import random
import time
import copy
black = pygame.Color(0, 0, 0)

class Snake():
    def __init__(self, game_window, controls, color, position, speed, window_specs,can_touch=True,respawn=True):

        self.game_window = game_window
        self.color = color
        self.window_x = window_specs[0]
        self.window_y = window_specs[1]

        self.can_touch = can_touch
        self.respawn = respawn

        self.snake_position = position.copy()
        self.snake_body = [position.copy()]
        self.speed = speed
        self.controls = controls.copy()
        
        self.direction = 'RIGHT'
        self.change_to = self.direction
        
        self.moving = True
        self.fps = pygame.time.Clock()
        self.start_moving()

    def start_moving(self):
        #Handles the controls and movement. Control first snake with 'wasd'. Control second snake with arrow keys.
        while self.moving == True:
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    if event.key == self.controls[0]:
                        self.change_to = 'UP'
                    if event.key == self.controls[1]:
                        self.change_to = 'LEFT'
                    if event.key == self.controls[2]:
                        self.change_to = 'DOWN'
                    if event.key == self.controls[3]:
                        self.change_to = 'RIGHT'
            if self.change_to == 'UP' and self.direction != 'DOWN':
                self.direction = 'UP'
            if self.change_to == 'DOWN' and self.direction != 'UP':
                self.direction = 'DOWN'
            if self.change_to == 'LEFT' and self.direction != 'RIGHT':
                self.direction = 'LEFT'
            if self.change_to == 'RIGHT' and self.direction != 'LEFT':
                self.direction = 'RIGHT'

            if self.direction == 'UP':
                self.snake_position[1] -= 10
            if self.direction == 'LEFT':
                self.snake_position[0] -= 10
            if self.direction == 'DOWN':
                self.snake_position[1]  = 10
            if self.direction == 'RIGHT':
                self.snake_position[0]  = 10
            self.snake_body.insert(0, list(self.snake_position))
            self.snake_body.pop()
            
            self.game_window.fill(black)
            for pos in self.snake_body:
                pygame.draw.rect(self.game_window,self.color,pygame.Rect(pos[0],pos[1],10,10))

            #Toggles whether snake will die if touching itself
            if self.can_touch == False:
                for block in self.snake_body[1:]:
                    if self.snake_position[0] == block[0] and self.snake_position[1] == block[1]:
                        self.die()

            #Kills snake if snake touches the edge of the window
            if self.snake_position[0] < 0 or self.snake_position[0] > (self.window_x - 10):
                self.die()
            if self.snake_position[1] < 0 or self.snake_position[1] > (self.window_y - 10):
                self.die()
            pygame.display.update()
            self.fps.tick(self.speed)

    def die(self):
        
        #Reset the snake
        self.moving = False
        self.game_window.fill(black)
        self.snake_body = []
        self.position = []
        pygame.display.update()
        time.sleep(1)
        
        #Makes the snake respawn in a random location
        if self.respawn == True:
            self.direction = 'RIGHT'
            self.change_to = self.direction
            self.snake_position = [random.randrange(1, (self.window_x//100)) * 10,
                     random.randrange(1, (self.window_y//100)) * 10]
            self.snake_body = [self.snake_position.copy()]
            self.moving = True
            self.start_moving()

class App():
    def __init__(self,num_players,can_touch=True,respawn=True):
        self.controls = [[pygame.K_w, pygame.K_a, pygame.K_s, pygame.K_d],
                                [pygame.K_UP,pygame.K_LEFT,pygame.K_DOWN,pygame.K_RIGHT]]
        self.starting_positions = [[100,50], [200, 100]]
        self.speed = 15
        self.window_specs = [720,480]
        self.num_players = num_players
        self.can_touch = can_touch
        self.respawn = respawn
        self.players = {}

        #Start up pygame
        pygame.init()
        pygame.display.set_caption('Snake Game')
        self.game_window = pygame.display.set_mode((self.window_specs[0], self.window_specs[1]))

        self.get_players()

    def get_players(self):
        #Let the players choose what color they want
        player_color = []
        for i in range(self.num_players):
            player_color.append(input(f'Player {i 1}, what color do you want your snake to be? '))
            if player_color[i] == 'red':
                player_color[i] = pygame.Color(255,0,0)
            elif player_color[i] == 'green':
                player_color[i] = pygame.Color(0,255,0)
            elif player_color[i] == 'blue':
                player_color[i] = pygame.Color(0,0,255)
            else:
                player_color[i] = pygame.Color(255,255,255)

        #Create the Snakes, this is the bit that does not work.
        for i in range(self.num_players):
            self.players[f'player{i 1}'] = Snake(self.game_window,self.controls[i],player_color[i],self.starting_positions[i],self.speed,self.window_specs)

if __name__ == "__main__":
    num_players = int(input('1 or 2 players: '))
    App(num_players)

CodePudding user response:

The issue here is that you did not set up a dedicated game loop for this game. The "game loop" for your game right now rests within an individual snake object. Your Snake.start_moving function is the thing that keeps on running and where you see your snake move etc. In the case where you create one snake, you are running that's snake's start_moving function, so everything looks fine on the surface.

If you are running with 2 snakes, the first snake's start_moving function is called and you are stuck there. The second snake is not yet created at all. To convince yourself, try adding a if event.key is q, return out of the function. Then you will see that when you ask for two snakes, the first snake will run, then you press q, the first snake will disappear and the second snake will appear.

The way to solve your issue is to make a dedicated game loop in your project, and delegate all of your game updats to there. There are a few different ways to do that so I'll leave that to you to design. But in broad strokes this is what I recommend in pseudocode:

snakes = [Snake() for _ in range(numplayers)]

while True:
    for event in pg.events():
        if event is keypress:
            for snake in snakes:
                snake.handle_keys(event.key)
            
    for snake in snakes:
        snake.update(dt)

    for snake in snakes:
        snake.draw(display)
    display.flip()
    tick() # or calculate dt

CodePudding user response:

You need one application loop for the entire application rather than a separate application for each snake. Remove the application loop from the Snake class:

class Snake():
    def __init__(self, game_window, controls, color, position, window_specs,can_touch=True,respawn=True):

        self.game_window = game_window
        self.color = color
        self.window_x = window_specs[0]
        self.window_y = window_specs[1]

        self.can_touch = can_touch
        self.respawn = respawn

        self.snake_position = position.copy()
        self.snake_body = [position.copy()]
        self.controls = controls.copy()
        self.direction = (1, 0)
        
    def move(self, event_list):
        #Handles the controls and movement. Control first snake with 'wasd'. Control second snake with arrow keys.
        for event in event_list:
            if event.type == pygame.QUIT:
                pygame.quit()
            if event.type == pygame.KEYDOWN:
                if event.key == self.controls[0] and self.direction[1] <= 0:
                    self.direction = (0, -1)
                if event.key == self.controls[1] and self.direction[0] <= 0:
                    self.direction = (-1, 0)
                if event.key == self.controls[2] and self.direction[1] >= 0:
                    self.direction = (0, 1)
                if event.key == self.controls[3] and self.direction[0] >= 0:
                    self.direction = (1, 0)

        self.snake_position[0]  = self.direction[0] * 10
        self.snake_position[1]  = self.direction[1] * 10
        self.snake_body.insert(0, list(self.snake_position))
        self.snake_body.pop()
        
        for pos in self.snake_body:
            pygame.draw.rect(self.game_window,self.color,pygame.Rect(pos[0],pos[1],10,10))

        #Toggles whether snake will die if touching itself
        if self.can_touch == False:
            for block in self.snake_body[1:]:
                if self.snake_position[0] == block[0] and self.snake_position[1] == block[1]:
                    self.die()

        #Kills snake if snake touches the edge of the window
        if self.snake_position[0] < 0 or self.snake_position[0] > (self.window_x - 10):
            self.die()
        if self.snake_position[1] < 0 or self.snake_position[1] > (self.window_y - 10):
            self.die()

    def die(self):
        self.snake_body = []
        self.position = []
        
        #Makes the snake respawn in a random location
        if self.respawn == True:
            self.direction = (1, 0)
            self.snake_position = [random.randrange(1, (self.window_x//100)) * 10,
                     random.randrange(1, (self.window_y//100)) * 10]
            self.snake_body = [self.snake_position.copy()]

But add an application loop in the App:

class App():
    # [...]

    def get_players(self):
        #Let the players choose what color they want
        player_color = []
        color_dict = {'red': (255, 0, 0), 'green': (0, 255, 0), 'blue': (0, 0, 255)}
        for i in range(self.num_players):
            color = input(f'Player {i 1}, what color do you want your snake to be? ')
            player_color.append(color_dict[color] if color in color_dict else (255, 255, 255))

        #Create the Snakes, this is the bit that does not work.
        for i in range(self.num_players):
            self.players[f'player{i 1}'] = Snake(self.game_window,self.controls[i],player_color[i],self.starting_positions[i],self.window_specs)

        clock = pygame.time.Clock()
        run = True
        while run: 
            clock.tick(self.speed)  
            event_list = pygame.event.get()
            for event in event_list:
                if event.type == pygame.QUIT:
                    run = False

            self.game_window.fill(black)  
            for key in self.players:
                self.players[key].move(event_list)
            pygame.display.update()     
                   
        pygame.quit()

  • Related