Task: Snake







Python: from __future__ import annotations

import itertools
import random

from enum import Enum

from typing import Any
from typing import Tuple

import pygame as pg

from pygame import Color
from pygame import Rect

from pygame.surface import Surface

from pygame.sprite import AbstractGroup
from pygame.sprite import Group
from pygame.sprite import RenderUpdates
from pygame.sprite import Sprite


class Direction(Enum):
    UP = (0, -1)
    DOWN = (0, 1)
    LEFT = (-1, 0)
    RIGHT = (1, 0)

    def opposite(self, other: Direction):
        return (self[0] + other[0], self[1] + other[1]) == (0, 0)

    def __getitem__(self, i: int):
        return self.value[i]


class SnakeHead(Sprite):
    def __init__(
        self,
        size: int,
        position: Tuple[int, int],
        facing: Direction,
        bounds: Rect,
    ) -> None:
        super().__init__()
        self.image = Surface((size, size))
        self.image.fill(Color("aquamarine4"))
        self.rect = self.image.get_rect()
        self.rect.center = position
        self.facing = facing
        self.size = size
        self.speed = size
        self.bounds = bounds

    def update(self, *args: Any, **kwargs: Any) -> None:
        
        self.rect.move_ip(
            (
                self.facing[0] * self.speed,
                self.facing[1] * self.speed,
            )
        )

        
        if self.rect.right > self.bounds.right:
            self.rect.left = 0
        elif self.rect.left < 0:
            self.rect.right = self.bounds.right

        if self.rect.bottom > self.bounds.bottom:
            self.rect.top = 0
        elif self.rect.top < 0:
            self.rect.bottom = self.bounds.bottom

    def change_direction(self, direction: Direction):
        if not self.facing == direction and not direction.opposite(self.facing):
            self.facing = direction


class SnakeBody(Sprite):
    def __init__(
        self,
        size: int,
        position: Tuple[int, int],
        colour: str = "white",
    ) -> None:
        super().__init__()
        self.image = Surface((size, size))
        self.image.fill(Color(colour))
        self.rect = self.image.get_rect()
        self.rect.center = position


class Snake(RenderUpdates):
    def __init__(self, game: Game) -> None:
        self.segment_size = game.segment_size
        self.colours = itertools.cycle(["aquamarine1", "aquamarine3"])

        self.head = SnakeHead(
            size=self.segment_size,
            position=game.rect.center,
            facing=Direction.RIGHT,
            bounds=game.rect,
        )

        neck = [
            SnakeBody(
                size=self.segment_size,
                position=game.rect.center,
                colour=next(self.colours),
            )
            for _ in range(2)
        ]

        super().__init__(*[self.head, *neck])

        self.body = Group()
        self.tail = neck[-1]

    def update(self, *args: Any, **kwargs: Any) -> None:
        self.head.update()

        
        segments = self.sprites()
        for i in range(len(segments) - 1, 0, -1):
            
            segments[i].rect.center = segments[i - 1].rect.center

    def change_direction(self, direction: Direction):
        self.head.change_direction(direction)

    def grow(self):
        tail = SnakeBody(
            size=self.segment_size,
            position=self.tail.rect.center,
            colour=next(self.colours),
        )
        self.tail = tail
        self.add(self.tail)
        self.body.add(self.tail)


class SnakeFood(Sprite):
    def __init__(self, game: Game, size: int, *groups: AbstractGroup) -> None:
        super().__init__(*groups)
        self.image = Surface((size, size))
        self.image.fill(Color("red"))
        self.rect = self.image.get_rect()

        self.rect.topleft = (
            random.randint(0, game.rect.width),
            random.randint(0, game.rect.height),
        )

        self.rect.clamp_ip(game.rect)

        
        
        while pg.sprite.spritecollideany(self, game.snake):
            self.rect.topleft = (
                random.randint(0, game.rect.width),
                random.randint(0, game.rect.height),
            )

            self.rect.clamp_ip(game.rect)


class Game:
    def __init__(self) -> None:
        self.rect = Rect(0, 0, 640, 480)
        self.background = Surface(self.rect.size)
        self.background.fill(Color("black"))

        self.score = 0
        self.framerate = 16

        self.segment_size = 10
        self.snake = Snake(self)
        self.food_group = RenderUpdates(SnakeFood(game=self, size=self.segment_size))

        pg.init()

    def _init_display(self) -> Surface:
        bestdepth = pg.display.mode_ok(self.rect.size, 0, 32)
        screen = pg.display.set_mode(self.rect.size, 0, bestdepth)

        pg.display.set_caption("Snake")
        pg.mouse.set_visible(False)

        screen.blit(self.background, (0, 0))
        pg.display.flip()

        return screen

    def draw(self, screen: Surface):
        dirty = self.snake.draw(screen)
        pg.display.update(dirty)

        dirty = self.food_group.draw(screen)
        pg.display.update(dirty)

    def update(self, screen):
        self.food_group.clear(screen, self.background)
        self.food_group.update()
        self.snake.clear(screen, self.background)
        self.snake.update()

    def main(self) -> int:
        screen = self._init_display()
        clock = pg.time.Clock()

        while self.snake.head.alive():
            for event in pg.event.get():
                if event.type == pg.QUIT or (
                    event.type == pg.KEYDOWN and event.key in (pg.K_ESCAPE, pg.K_q)
                ):
                    return self.score

            
            keystate = pg.key.get_pressed()

            if keystate[pg.K_RIGHT]:
                self.snake.change_direction(Direction.RIGHT)
            elif keystate[pg.K_LEFT]:
                self.snake.change_direction(Direction.LEFT)
            elif keystate[pg.K_UP]:
                self.snake.change_direction(Direction.UP)
            elif keystate[pg.K_DOWN]:
                self.snake.change_direction(Direction.DOWN)

            
            self.update(screen)

            
            for food in pg.sprite.spritecollide(
                self.snake.head, self.food_group, dokill=False
            ):
                food.kill()
                self.snake.grow()
                self.score += 1

                
                if self.score % 5 == 0:
                    self.framerate += 1

                self.food_group.add(SnakeFood(self, self.segment_size))

            
            if pg.sprite.spritecollideany(self.snake.head, self.snake.body):
                self.snake.head.kill()

            self.draw(screen)
            clock.tick(self.framerate)

        return self.score


if __name__ == "__main__":
    game = Game()
    score = game.main()
    print(score)





Ada: 



with Ada.Containers; use Ada.Containers;
with Ada.Containers.Vectors;

package Snake is
   
   
   
   type directions is (Up, Down, Right, Left, Quit, Restart, Invalid);
   
   
   function Get_Immediate return Character;

   Arrow_Prefix : constant Character := Character'Val(224); 

   function Get_Keystroke return directions;
   
   
   
   type gameBoard is array (Natural range <>, Natural range <>) of Character;
   
   
   procedure init;
   
   
   procedure run;  
   
   
   procedure Display_Board;
   
   procedure ClearBoard;
   
   
   type coord is tagged record
      X,Y : Integer;
   end record;

   
   package snakeBody is new Ada.Containers.Vectors (Natural, coord);
   use snakeBody;
   
   
   function moveSnake return Boolean;
   
   
   procedure generateFood;
   
   
   procedure addDataToBoard;
   
   
   function getRandomInteger(upperBound : Integer) return Integer;
   
private
   
   width, height : Positive := 10;
   board : gameBoard := (0 .. (width+1) => (0 .. (height+1) => ' '));
   snake : Vector := (1,1) & (2,1) & (3,1);
   snakeDirection : directions := Right;
   
   food : coord := (5,5);

end Snake;




with Ada.Text_IO; use Ada.Text_IO;
with Ada.Numerics.Discrete_Random;

package body Snake is
   
   
   
   function Get_Immediate return Character is
   begin
      return Answer : Character do Ada.Text_IO.Get_Immediate(Answer);
      end return;
   end Get_Immediate;

   
   
   function Get_Keystroke return directions is
     (case Get_Immediate is
         when 'Q' | 'q' => Quit,
         when 'R' | 'r' => Restart,
         when 'W' | 'w' => Left,
         when 'A' | 'a' => Up,
         when 'S' | 's' => Down,
         when 'D' | 'd' => Right,
         
         when Arrow_Prefix => (case Character'Pos(Get_Immediate) is
                                  when 72 => Up,
                                  when 75 => Left,
                                  when 77 => Right,
                                  when 80 => Down,
                                  when others => Invalid),
         
         when ASCII.ESC => (case Get_Immediate is
                               when '[' => (case Get_Immediate is
                                               when 'A' => Up,
                                               when 'B' => Down,
                                               when 'C' => Right,
                                               when 'D' => Left,
                                               when others => Invalid),
                               when others => Invalid),
         when others => Invalid);
   
   
   procedure init is
   begin
      
      for Row in 0 .. width+1 loop
         for Column in 0 .. height+1 loop
            
            if Row = 0 or Row = width+1 then
               board(Row,Column) := '#';
               
            else
               if Column = 0 or Column = height+1 then
                  board(Row,Column) := '#';
                  
               else
                  board(Row,Column) := ' ';
                  
               end if;
            end if;
            
         end loop;
      end loop;  
   end;

   
   procedure run is
      
      task T;
      task body T is
      begin
         loop
            snakeDirection := Get_Keystroke;
         end loop;
      end T;
      
   begin
      
      init;
      loop
         exit when snakeDirection = Quit;
      
         if moveSnake = False then
            Put_Line("GAME OVER!!!");
            Put_Line("Your score is:" & Length(snake)'Image);
            exit;
         end if;
      
      
         Display_Board;
         delay 0.7;
         
      end loop;
   end run;
   
   
   procedure Display_Board is
   begin
      
      for Row in 0 .. width+1 loop
         for Column in 0 .. height+1 loop
            Put(board(Row, Column));
         end loop;
         New_Line;
      end loop; 
      
   end Display_Board;
   
   
   procedure ClearBoard is
   begin
      
      for Row in 1 .. width loop
         for Column in 1 .. height loop
            board(Row, Column) := ' ';
         end loop;
      end loop; 
      
   end ClearBoard;
   
   
   function moveSnake return Boolean is
      colision : Boolean := False;
      headCoord : coord := Snake.First_Element;
      addSnakePart : Boolean := False;
   begin
      
      case snakeDirection is
         when Up => headCoord.X := headCoord.X - 1;
         when Down => headCoord.X := headCoord.X + 1;
         when Right => headCoord.Y := headCoord.Y + 1;
         when Left => headCoord.Y := headCoord.Y - 1;
         when others => null;
      end case;
      
      if headCoord.Y = height+1 then
         return False;
      end if;
      
      if headCoord.Y = 0 then
         return False;
      end if;
      
      if headCoord.X = width+1 then
         return False;
      end if;
      
      if headCoord.X = 0 then
         return False;
      end if;
      
      for index in snake.Iterate loop
         if headCoord = snake(To_Index(index)) then
            return False;
         end if;
      end loop;

      snake.Prepend(headCoord);
      
      if headCoord /= food then
         snake.Delete_Last;
      else
         food := (0,0);
         generateFood;
      end if;
      
      addDataToBoard;

      return True;
   end moveSnake;
   
   
   procedure generateFood is
   begin
      if food.X = 0 or food.Y = 0 then
         food := (getRandomInteger(width), getRandomInteger(height));
      end if;
   end generateFood;
   
   
   procedure addDataToBoard is
   begin
      ClearBoard;
      
      board(food.X, food.Y) := '*';
      
      for index in snake.Iterate loop
         board(snake(To_Index(index)).X, snake(To_Index(index)).Y) := 'o';
      end loop;
      
   end addDataToBoard;

   
   function getRandomInteger(upperBound : Integer) return Integer is

      subtype Random_Range is Integer range 1 .. upperBound;

      package R is new
        Ada.Numerics.Discrete_Random (Random_Range);
      use R;

      G : Generator;
   begin
      Reset (G);
      return Random (G);
   end getRandomInteger;

end Snake;




with Snake;

procedure Main is
begin
   Snake.run;
end;