Dissable boundaries in a python snake game
Question: > My problem is hard to explain so please run the code to see what's going on

I'm trying to adapt the code of a snake game so that the snake cannot collide
with the screen boundaries. I'm using existing code that can be found here
[code on
stackexchange](http://codereview.stackexchange.com/questions/24267/snake-game-
made-with-python). I'm using the revised version by [Gareth
Rees](http://codereview.stackexchange.com/users/11728/gareth-rees) (see the
bottom part of his answer). I added the no boundaries option (global boolean
`WORLD_BOUNDARIES = False`) but I have problems to get it right basically
because I'm not used to pythons syntax.

My adapted code looks like this. The changed/added code is between
`'''****...'''` comments inside the snake class' update function). The code
below can be safely executed but has some bugs related to the no boundaries. I
did try to fix the bug in the down direction but it is not working so I
commented it out.

    
    
    from collections import deque
    from itertools import *
    import pygame
    from random import randrange
    import sys
    from pygame.locals import *
    
    class Vector(tuple):
        """A tuple that supports some vector operations.
    
        >>> v, w = Vector((1, 2)), Vector((3, 4))
        >>> v + w, w - v, v * 10, 100 * v, -v
        ((4, 6), (2, 2), (10, 20), (100, 200), (-1, -2))
        """
        def __add__(self, other): return Vector(v + w for v, w in zip(self, other))
        def __radd__(self, other): return Vector(w + v for v, w in zip(self, other))
        def __sub__(self, other): return Vector(v - w for v, w in zip(self, other))
        def __rsub__(self, other): return Vector(w - v for v, w in zip(self, other))
        def __mul__(self, s): return Vector(v * s for v in self)
        def __rmul__(self, s): return Vector(v * s for v in self)
        def __neg__(self): return -1 * self
    
    FPS = 60                        # Game frames per second
    SEGMENT_SCORE = 50              # Score per segment
    
    SNAKE_SPEED_INITIAL = 4.0       # Initial snake speed (squares per second)
    SNAKE_SPEED_INCREMENT = 0.25    # Snake speeds up this much each time it grows
    SNAKE_START_LENGTH = 4          # Initial snake length in segments
    
    WORLD_SIZE = Vector((20, 20))   # World size, in blocks
    BLOCK_SIZE = 24                 # Block size, in pixels
    WORLD_BOUNDARIES = False
    
    BACKGROUND_COLOR = 45, 45, 45
    SNAKE_COLOR = 0, 255, 0
    FOOD_COLOR = 255, 0, 0
    DEATH_COLOR = 255, 0, 0
    TEXT_COLOR = 255, 255, 255
    
    DIRECTION_UP    = Vector(( 0, -1))
    DIRECTION_DOWN  = Vector(( 0,  1))
    DIRECTION_LEFT  = Vector((-1,  0))
    DIRECTION_RIGHT = Vector(( 1,  0))
    DIRECTION_DR    = DIRECTION_DOWN + DIRECTION_RIGHT
    
    # Map from PyGame key event to the corresponding direction.
    KEY_DIRECTION = {
        K_w: DIRECTION_UP,    K_UP:    DIRECTION_UP,   
        K_s: DIRECTION_DOWN,  K_DOWN:  DIRECTION_DOWN, 
        K_a: DIRECTION_LEFT,  K_LEFT:  DIRECTION_LEFT, 
        K_d: DIRECTION_RIGHT, K_RIGHT: DIRECTION_RIGHT,
    }
    
    class Snake(object):
        def __init__(self, start, start_length):
            self.speed = SNAKE_SPEED_INITIAL # Speed in squares per second.
            self.timer = 1.0 / self.speed    # Time remaining to next movement.
            self.growth_pending = 0          # Number of segments still to grow.
            self.direction = DIRECTION_UP    # Current movement direction.
            self.segments = deque([start - self.direction * i
                                 for i in xrange(start_length)])
    
        def __iter__(self):
            return iter(self.segments)
    
        def __len__(self):
            return len(self.segments)
    
        def change_direction(self, direction):
            """Update the direction of the snake."""
            # Moving in the opposite direction of current movement is not allowed.
            if self.direction != -direction:
                self.direction = direction
    
        def head(self):
            """Return the position of the snake's head."""
            return self.segments[0]
    
        def tail(self):
            """Return the tail of the snake."""
            return deque(islice(self.segments, 1, None))
    
        def update(self, dt, direction):
            """Update the snake by dt seconds and possibly set direction."""
            self.timer -= dt
            if self.timer > 0:
                # Nothing to do yet.
                return
            # Moving in the opposite direction of current movement is not allowed.
            if self.direction != -direction:
                self.direction = direction
            self.timer += 1 / self.speed
            '''************************************************************************************
            ***************************************************************************************
            ***************************************************************************************'''
            if not WORLD_BOUNDARIES:
                head = self.head()
                if self.direction == DIRECTION_DOWN and head[1] == WORLD_SIZE[1]-1:
                    self.segments[0]=Vector((head[0], 0))
                    # new_head = (head[0], 0)
                    # new_tail = deque([x+self.direction for x in self.tail()])
                    # self.segments = new_tail.extendleft([new_head])
                    # print new_head
                    # print new_tail
                if self.direction == DIRECTION_UP and head[1] == 0:
                    self.segments[0]=Vector((head[0], WORLD_SIZE[1]-1))
                if self.direction == DIRECTION_RIGHT and head[0] == WORLD_SIZE[0]-1:
                    self.segments[0]=Vector((0, head[1]))
                if self.direction == DIRECTION_LEFT and head[0] == 0:
                    self.segments[0]=Vector((WORLD_SIZE[0]-1, head[1]))
            '''************************************************************************************
            ***************************************************************************************
            ***************************************************************************************'''
    
            # Add a new head.
            self.segments.appendleft(self.head() + self.direction)
            if self.growth_pending > 0:
                self.growth_pending -= 1
            else:
                # Remove tail.
                self.segments.pop()
    
        def grow(self):
            """Grow snake by one segment and speed up."""
            self.growth_pending += 1
            self.speed += SNAKE_SPEED_INCREMENT
    
        def self_intersecting(self):
            """Is the snake currently self-intersecting?"""
            it = iter(self)
            head = next(it)
            return head in it
    
    class SnakeGame(object):
        def __init__(self):
            pygame.display.set_caption('PyGame Snake')
            self.block_size = BLOCK_SIZE
            self.window = pygame.display.set_mode(WORLD_SIZE * self.block_size)
            self.screen = pygame.display.get_surface()
            self.clock = pygame.time.Clock()
            self.font = pygame.font.Font('freesansbold.ttf', 20)
            self.world = Rect((0, 0), WORLD_SIZE)
            self.reset()
    
        def reset(self):
            """Start a new game."""
            self.playing = True
            self.next_direction = DIRECTION_UP
            self.score = 0
            self.snake = Snake(self.world.center, SNAKE_START_LENGTH)
            self.food = set()
            self.add_food()
    
        def add_food(self):
            """Ensure that there is at least one piece of food.
            (And, with small probability, more than one.)
            """
            while not (self.food and randrange(4)):
                food = Vector(map(randrange, self.world.bottomright))
                if food not in self.food and food not in self.snake:
                    self.food.add(food)
    
        def input(self, e):
            """Process keyboard event e."""
            if e.key in KEY_DIRECTION:
                self.next_direction = KEY_DIRECTION[e.key]
            elif e.key == K_SPACE and not self.playing:
                self.reset()
    
        def update(self, dt):
            """Update the game by dt seconds."""
            self.snake.update(dt, self.next_direction)
    
            # If snake hits a food block, then consume the food, add new
            # food and grow the snake.
            head = self.snake.head()
            if head in self.food:
                self.food.remove(head)
                self.add_food()
                self.snake.grow()
                self.score += len(self.snake) * SEGMENT_SCORE
    
            # If snake collides with self or the screen boundaries, then
            # it's game over.
            if self.snake.self_intersecting() or not self.world.collidepoint(self.snake.head()):
                self.playing = False
    
        def block(self, p):
            """Return the screen rectangle corresponding to the position p."""
            return Rect(p * self.block_size, DIRECTION_DR * self.block_size)
    
        def draw_text(self, text, p):
            """Draw text at position p."""
            self.screen.blit(self.font.render(text, 1, TEXT_COLOR), p)
    
        def draw(self):
            """Draw game (while playing)."""
            self.screen.fill(BACKGROUND_COLOR)
            for p in self.snake:
                pygame.draw.rect(self.screen, SNAKE_COLOR, self.block(p))
            for f in self.food:
                pygame.draw.rect(self.screen, FOOD_COLOR, self.block(f))
            self.draw_text("Score: {}".format(self.score), (20, 20))
    
        def draw_death(self):
            """Draw game (after game over)."""
            self.screen.fill(DEATH_COLOR)
            self.draw_text("Game over! Press Space to start a new game", (20, 150))
            self.draw_text("Your score is: {}".format(self.score), (140, 180))
    
        def play(self):
            """Play game until the QUIT event is received."""
            while True:
                dt = self.clock.tick(FPS) / 1000.0 # convert to seconds
                for e in pygame.event.get():
                    if e.type == QUIT:
                        return
                    elif e.type == KEYUP:
                        self.input(e)
                if self.playing: 
                    self.update(dt)
                    self.draw()
                else:
                    self.draw_death()
                pygame.display.flip()
    
    if __name__ == '__main__':
    
        pygame.init()
        SnakeGame().play()
        pygame.quit()
    


Answer: after some thinking I managed to adapt the code so the snake can cross the
screen boundaries without bugs. It wasn't actually that difficult after all.
Those who are interested can see the code below:

    
    
    from collections import deque
    from itertools import *
    import pygame
    from random import randrange
    import sys
    from pygame.locals import *
    
    class Vector(tuple):
        """A tuple that supports some vector operations.
    
        >>> v, w = Vector((1, 2)), Vector((3, 4))
        >>> v + w, w - v, v * 10, 100 * v, -v
        ((4, 6), (2, 2), (10, 20), (100, 200), (-1, -2))
        """
        def __add__(self, other): return Vector(v + w for v, w in zip(self, other))
        def __radd__(self, other): return Vector(w + v for v, w in zip(self, other))
        def __sub__(self, other): return Vector(v - w for v, w in zip(self, other))
        def __rsub__(self, other): return Vector(w - v for v, w in zip(self, other))
        def __mul__(self, s): return Vector(v * s for v in self)
        def __rmul__(self, s): return Vector(v * s for v in self)
        def __neg__(self): return -1 * self
    
    FPS = 60                        # Game frames per second
    SEGMENT_SCORE = 50              # Score per segment
    
    SNAKE_SPEED_INITIAL = 4.0       # Initial snake speed (squares per second)
    SNAKE_SPEED_INCREMENT = 0.25    # Snake speeds up this much each time it grows
    SNAKE_START_LENGTH = 4          # Initial snake length in segments
    
    WORLD_SIZE = Vector((20, 20))   # World size, in blocks
    BLOCK_SIZE = 24                 # Block size, in pixels
    WORLD_BOUNDARIES = False
    
    BACKGROUND_COLOR = 45, 45, 45
    SNAKE_COLOR = 0, 255, 0
    FOOD_COLOR = 255, 0, 0
    DEATH_COLOR = 255, 0, 0
    TEXT_COLOR = 255, 255, 255
    
    DIRECTION_UP    = Vector(( 0, -1))
    DIRECTION_DOWN  = Vector(( 0,  1))
    DIRECTION_LEFT  = Vector((-1,  0))
    DIRECTION_RIGHT = Vector(( 1,  0))
    DIRECTION_DR    = DIRECTION_DOWN + DIRECTION_RIGHT
    
    # Map from PyGame key event to the corresponding direction.
    KEY_DIRECTION = {
        K_w: DIRECTION_UP,    K_UP:    DIRECTION_UP,   
        K_s: DIRECTION_DOWN,  K_DOWN:  DIRECTION_DOWN, 
        K_a: DIRECTION_LEFT,  K_LEFT:  DIRECTION_LEFT, 
        K_d: DIRECTION_RIGHT, K_RIGHT: DIRECTION_RIGHT,
    }
    
    class Snake(object):
        def __init__(self, start, start_length):
            self.speed = SNAKE_SPEED_INITIAL # Speed in squares per second.
            self.timer = 1.0 / self.speed    # Time remaining to next movement.
            self.growth_pending = 0          # Number of segments still to grow.
            self.direction = DIRECTION_UP    # Current movement direction.
            self.segments = deque([start - self.direction * i
                                 for i in xrange(start_length)])
    
        def __iter__(self):
            return iter(self.segments)
    
        def __len__(self):
            return len(self.segments)
    
        def change_direction(self, direction):
            """Update the direction of the snake."""
            # Moving in the opposite direction of current movement is not allowed.
            if self.direction != -direction:
                self.direction = direction
    
        def head(self):
            """Return the position of the snake's head."""
            return self.segments[0]
    
        def tail(self):
            """Return the tail of the snake."""
            return deque(islice(self.segments, 1, None))
    
        def update(self, dt, direction):
            """Update the snake by dt seconds and possibly set direction."""
            self.timer -= dt
            if self.timer > 0:
                # Nothing to do yet.
                return
            # Moving in the opposite direction of current movement is not allowed.
            if self.direction != -direction:
                self.direction = direction
            self.timer += 1 / self.speed
    
            '''************************************************************************************
            ***************************************************************************************
            ***************************************************************************************'''
            # Add a new head.
            if not WORLD_BOUNDARIES:
                head = self.head()
                if self.direction == DIRECTION_DOWN and head[1] == WORLD_SIZE[1]-1:
                    self.segments.appendleft(Vector((head[0], 0)))
    
                elif self.direction == DIRECTION_UP and head[1] == 0:
                    self.segments.appendleft(Vector((head[0], WORLD_SIZE[1]-1)))
    
                elif self.direction == DIRECTION_RIGHT and head[0] == WORLD_SIZE[0]-1:
                    self.segments.appendleft(Vector((0, head[1])))
    
                elif self.direction == DIRECTION_LEFT and head[0] == 0:
                    self.segments.appendleft(Vector((WORLD_SIZE[0]-1, head[1])))
    
                else: self.segments.appendleft(self.head() + self.direction)
            else: self.segments.appendleft(self.head() + self.direction)
    
            '''************************************************************************************
            ***************************************************************************************
            ***************************************************************************************'''
    
    
    
            if self.growth_pending > 0:
                self.growth_pending -= 1
            else:
                # Remove tail.
                self.segments.pop()
    
        def grow(self):
            """Grow snake by one segment and speed up."""
            self.growth_pending += 1
            self.speed += SNAKE_SPEED_INCREMENT
    
        def self_intersecting(self):
            """Is the snake currently self-intersecting?"""
            it = iter(self)
            head = next(it)
            return head in it
    
    class SnakeGame(object):
        def __init__(self):
            pygame.display.set_caption('PyGame Snake')
            self.block_size = BLOCK_SIZE
            self.window = pygame.display.set_mode(WORLD_SIZE * self.block_size)
            self.screen = pygame.display.get_surface()
            self.clock = pygame.time.Clock()
            self.font = pygame.font.Font('freesansbold.ttf', 20)
            self.world = Rect((0, 0), WORLD_SIZE)
            self.reset()
    
        def reset(self):
            """Start a new game."""
            self.playing = True
            self.next_direction = DIRECTION_UP
            self.score = 0
            self.snake = Snake(self.world.center, SNAKE_START_LENGTH)
            self.food = set()
            self.add_food()
    
        def add_food(self):
            """Ensure that there is at least one piece of food.
            (And, with small probability, more than one.)
            """
            while not (self.food and randrange(4)):
                food = Vector(map(randrange, self.world.bottomright))
                if food not in self.food and food not in self.snake:
                    self.food.add(food)
    
        def input(self, e):
            """Process keyboard event e."""
            if e.key in KEY_DIRECTION:
                self.next_direction = KEY_DIRECTION[e.key]
            elif e.key == K_SPACE and not self.playing:
                self.reset()
    
        def update(self, dt):
            """Update the game by dt seconds."""
            self.snake.update(dt, self.next_direction)
    
            # If snake hits a food block, then consume the food, add new
            # food and grow the snake.
            head = self.snake.head()
            if head in self.food:
                self.food.remove(head)
                self.add_food()
                self.snake.grow()
                self.score += len(self.snake) * SEGMENT_SCORE
    
            # If snake collides with self or the screen boundaries, then
            # it's game over.
            if self.snake.self_intersecting() or not self.world.collidepoint(self.snake.head()):
                self.playing = False
    
        def block(self, p):
            """Return the screen rectangle corresponding to the position p."""
            return Rect(p * self.block_size, DIRECTION_DR * self.block_size)
    
        def draw_text(self, text, p):
            """Draw text at position p."""
            self.screen.blit(self.font.render(text, 1, TEXT_COLOR), p)
    
        def draw(self):
            """Draw game (while playing)."""
            self.screen.fill(BACKGROUND_COLOR)
            for p in self.snake:
                pygame.draw.rect(self.screen, SNAKE_COLOR, self.block(p))
            for f in self.food:
                pygame.draw.rect(self.screen, FOOD_COLOR, self.block(f))
            self.draw_text("Score: {}".format(self.score), (20, 20))
    
        def draw_death(self):
            """Draw game (after game over)."""
            self.screen.fill(DEATH_COLOR)
            self.draw_text("Game over! Press Space to start a new game", (20, 150))
            self.draw_text("Your score is: {}".format(self.score), (140, 180))
    
        def play(self):
            """Play game until the QUIT event is received."""
            while True:
                dt = self.clock.tick(FPS) / 1000.0 # convert to seconds
                for e in pygame.event.get():
                    if e.type == QUIT:
                        return
                    elif e.type == KEYUP:
                        self.input(e)
                if self.playing: 
                    self.update(dt)
                    self.draw()
                else:
                    self.draw_death()
                pygame.display.flip()
    
    if __name__ == '__main__':
    
        pygame.init()
        SnakeGame().play()
        pygame.quit() 
    

