Snake

From Rosetta Code
Task
Snake
You are encouraged to solve this task according to the task description, using any language you may know.
This page uses content from Wikipedia. The original article was at Snake_(video_game). The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance)



Snake is a game where the player maneuvers a line which grows in length every time the snake reaches a food source.


Task

Implement a variant of the Snake game, in any interactive environment, in which a sole player attempts to eat items by running into them with the head of the snake.

Each item eaten makes the snake longer and a new item is randomly generated somewhere else on the plane.

The game ends when the snake attempts to eat himself.

Ada

-- This code is a basic implementation of snake in Ada using the command prompt
-- feel free to improve it!

-- Snake.ads
with Ada.Containers; use Ada.Containers;
with Ada.Containers.Vectors;

package Snake is
   
   -- copy from 2048 game (
   -- Keyboard management
   type directions is (Up, Down, Right, Left, Quit, Restart, Invalid);
   
   -- Redefining this standard procedure as function to allow Get_Keystroke as an expression function
   function Get_Immediate return Character;

   Arrow_Prefix : constant Character := Character'Val(224); -- works for windows

   function Get_Keystroke return directions;
   -- )
   
   -- The container for game data
   type gameBoard is array (Natural range <>, Natural range <>) of Character;
   
   -- Initilize the board
   procedure init;
   
   -- Run the game
   procedure run;  
   
   -- Displaying the board
   procedure Display_Board;
   -- Clear the board from content
   procedure ClearBoard;
   
   -- coordinates data structure
   type coord is tagged record
      X,Y : Integer;
   end record;

   -- list of coordinate (one coordinate for each body part of the snake)
   package snakeBody is new Ada.Containers.Vectors (Natural, coord);
   use snakeBody;
   
   -- update snake's part depending on the snakeDirection and checking colicion (with borders and snak's part)
   function moveSnake return Boolean;
   
   -- generate food if it was eaten
   procedure generateFood;
   
   -- Add snake and food to the board
   procedure addDataToBoard;
   
   -- generate random integer between 1 and upperBound to generate random food position
   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;


-- Snake.adb

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

package body Snake is
   
   -----------------------------------------------------------------------------
   -- Redefining this standard procedure as function to allow Get_Keystroke as an expression function (copy from 2048 game)
   function Get_Immediate return Character is
   begin
      return Answer : Character do Ada.Text_IO.Get_Immediate(Answer);
      end return;
   end Get_Immediate;

   -----------------------------------------------------------------------------
   -- (copy from 2048 game)
   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,
         -- Windows terminal
         when Arrow_Prefix => (case Character'Pos(Get_Immediate) is
                                  when 72 => Up,
                                  when 75 => Left,
                                  when 77 => Right,
                                  when 80 => Down,
                                  when others => Invalid),
         -- Unix escape sequences
         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) := '#';
               --  Insert(board(Row,0), Column, '#');
            else
               if Column = 0 or Column = height+1 then
                  board(Row,Column) := '#';
                  --  Insert(board, board(Row, Column), "#");
               else
                  board(Row,Column) := ' ';
                  --  Insert(board, 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;


-- main.adb

with Snake;

procedure Main is
begin
   Snake.run;
end;
Output:
############                                                                                                            
#          #                                                                                                            
#          #                                                                                                            
#    ooo   #                                                                                                            
#      o   #                                                                                                            
#      o   #                                                                                                            
#      o   #                                                                                                            
#      o   #                                                                                                            
#      o   #                                                                                                            
#     *    #                                                                                                            
#          #                                                                                                            
############ 


Amazing Hopper

Translation of: C
/* Snake */
/* Implementing this task in Hopper-FLOW-MATIC++ */
/* The snake must catch a bite before time runs out, which decreases by
   10 points every 800 milliseconds. 
   The remaining time will be added to your total score. */

#include <flow.h>
#include <flow-term.h>
#include <keys.h>

#define LIMIT_TIME      120
#define INF_TIME        80  //70
#define ONE_SECOND      1000
#define TOTAL_TIME      100
#define DECREMENT_TIME  10
#define COLOR_FOOD      232
#define BACK_FOOD       255
#define TIME_LEVEL      90000 //90000  // un minuto y medio
#define TIME_CHALLENGE  40000  // 40 segundos
#define TIME_LOST       30000
#define COLUMNA_PRINT   55
#define SIZE_INITIAL    -7

#enum  1,N,E,S,W
#enum  1,SPACE,FOOD,BORDER

DEF-MAIN 
   BREAK-ON
   STACK 16

   MSET(C, quit, nHead, dir, Size, SCORE, counter, T, TPlay,TSound, ConsPrey,Consumed stage )

   SET(len,0)
   
   SET( w, 50) 
   SET( h, 28) 
   SET( color food, COLOR_FOOD )
   SET( back food, BACK_FOOD )
   SET( TIME, 100 )
   LET( Size :=  MUL(w,h) )
   SET( LEVEL,1 )
   SET( REAL LEVEL,1 )
   FALSE(swPierde)
   TRUE(swExtra1,swExtra2)
   
   TRUE( tiles )
   SET( back tiles,1 ), MSET(tile1, tile2)
   VOID( head back tiles )
   MEM("\033[48;5;28m","\033[48;5;29m","\033[48;5;62m","\033[48;5;63m","\033[48;5;70m","\033[48;5;71m","\033[48;5;4m","\033[48;5;27m" )
   MEM("\033[48;5;99m","\033[48;5;97m","\033[48;5;17m","\033[48;5;18m","\033[48;5;62m","\033[48;5;63m")
   APND-LST(head back tiles)
   [back tiles] GET(head back tiles), MOVE-TO(tile1)
   [PLUS-ONE(back tiles)] GET(head back tiles), MOVE-TO(tile2)
   
   VOID( big number, numL1, numL2, numL3 )

   VOID( sounds ), SET( sound index, 1 )
   MEM("Snake_music.wav","Snake_music_l3.wav","Snake_music_l5.wav","Snake_music_l4.wav","Snake_music_l6.wav")
   APND-LST(sounds)
   
   GOSUB( set score )

   MSET(tBoard1,tmpLimit,Maze,wM,hM,SizeM)
   DIM( Size ) AS-ONES( board ), {board} MOVE-TO(tBoard1)

  // load and prepare maze for challenge stage:
      SET(i,1)
      LET( Maze := REPLICATE("3",50) )
      
      PERF-UP(i,26,1) 
         LET( Maze := CAT( Maze, CAT( CAT("3", REPLICATE("1",48) ), "3") ) )
      NEXT
      LET( Maze := CAT( Maze, REPLICATE("3",50) ))
      GOSUB( prepare maze )
           
   HIDE-CURSOR
   CLR-SCR

   SET( TLimit := LIMIT_TIME )
   MEM(SIZE_INITIAL),GOSUB( start )
   SET( time out, TIME_LEVEL)

   SYS( CAT(CAT("aplay fl/",[sound index]CGET(sounds)), "</dev/null >/dev/null 2>&1 &"))
   GOSUB( titles )
   
/* PLAY GAME!! */
   SET(lives,3)
   WHILE( lives )
      GOSUB( show ) 
      GOSUB( put titles )
      GOSUB( ready ), SLEEP(1.5)
      TIC( T ), TIC( TPlay ), TIC(TSound)
      KBD-FREE
      WHILE( NOT (quit) ) 
         ON-TIME( TLimit ~= TPlay ){
            GOSUB( show )
            WHEN( KEY-PRESSED? ){ 
               SCAN-CODE( C )
               SELECT( C ) 
                  CASE( K_UP )   { dir = N, EXIT }
                  CASE( K_RIGHT ){ dir = E, EXIT }
                  CASE( K_DOWN ) { dir = S, EXIT }
                  CASE( K_LEFT ) { dir = W, EXIT }
                  CASE( K_ESC )  { quit = 1, EXIT }
                  CASE( 32 )  { PAUSE, EXIT }
               SELEND
            }
            GOSUB( step )
         }
         ON-TIME( ONE_SECOND ~= T ) {
            TIME-=DECREMENT_TIME, CLAMP(0,TOTAL_TIME,TIME)
            {TIME, 12, COLUMNA_PRINT} GOSUB( put time )
         }
         ON-TIME( time out ~= TSound ){
            GOSUB(LEVELCLEAR)
         }
      WEND
   
      COND( OR(EQ?(LEVEL,3),EQ?(LEVEL,6)) )
         GOSUB(you lost),SLEEP(3)
         SET(quit,0), [nHead]{len}CPUT(board)
         LOCATE(1,1), FILL-BOX(" ",29,54)
         GOSUB( another play )
      ELS
         GOSUB(you lost),   SLEEP(3)
         --lives
         SET(quit,0)
         TRUE(swPierde)
         {len}GOSUB( start )
         COND( IS-NOT-ZERO?(lives) )
            SYS("aplay fl/Snake_music_vida_repechaje.wav </dev/null >/dev/null 2>&1 &")
         CEND
      CEND

   WEND
   
/* GAME OVER */
   GOSUB(game over ), SLEEP(7)
   SET-VIDEO-TEXT
   SHOW-CURSOR
   PRNL
END

RUTINES

DEF-FUN( KILLSOUND )
  SET( PID,0 )
  LET( PID := ONLY-CHR("0123456789 ", EXEC( "pidof aplay" )))
  COND( LEN(PID) )
     SYS( CAT(CAT("kill ",PID), " </dev/null >/dev/null 2>&1"))
  CEND
RET

// initialize the board, plant a very first food item
DEF-FUN( start, initial pos )

   COND( NOT-EQ?(LEVEL,3) )
      {tBoard1} MOVE-TO(board)
      {50,28},MUL(50,28), MOVE-TO( w, h, Size )
      
      [1:w] {BORDER} CPUT(board)  // top
      [ SUB(MUL(h,w),MINUS-ONE(w)) : end] {BORDER} CPUT(board) // bottom
      [1:w:end] {BORDER} CPUT(board)  // left

      SET(i, 1)
      FOR( LE?(i, h), ++i )
         [ MUL(i, w )] {BORDER} PUT(board)  // right
      NEXT
      LET( time out:=TIME_LEVEL )
   
   ELS
      TRASH(board)
   CEND
   
   SELECT(LEVEL)
      CASE(2){
         SET(i,3)
         PERF-UP(i,5,1)
            // laterales extremos:
             [ADD(MUL(i,w),5):ADD(MUL(i,w),8)]               {BORDER} CPUT(board)
             [ADD(MUL(i,w),SUB(w,7)):ADD(MUL(i,w),SUB(w,4))] {BORDER} CPUT(board)
            // laterales fondo
             [ADD(MUL(ADD(i,19),w),5):ADD(MUL(ADD(i,19),w),8)]               {BORDER} CPUT(board)
             [ADD(MUL(ADD(i,19),w),SUB(w,7)):ADD(MUL(ADD(i,19),w),SUB(w,4))] {BORDER} CPUT(board)

         NEXT
         EXIT
      }
      CASE(3){  // challenge stage!
         SPLIT(Maze,board, "" ), VAL(board), MOVE-TO(board)
         [1:w] {BORDER}CPUT(board)
         SET(i,1)
         
         PERF-UP(i,19,1)
             GOSUB(plant)
         NEXT
         LET(time out := TIME_CHALLENGE) 
         EXIT
      }
      CASE(4){
         SET(i,3)
         PERF-UP(i,5,1)
            // laterales medios
             [ADD(MUL(ADD(i,9),w),5):ADD(MUL(ADD(i,9),w),8)]               {BORDER} CPUT(board)
             [ADD(MUL(ADD(i,9),w),SUB(w,7)):ADD(MUL(ADD(i,9),w),SUB(w,4))] {BORDER} CPUT(board)
            // medio arriba
             [ADD(MUL(i,w),17):ADD(MUL(i,w),20)] {BORDER} CPUT(board)
             [ADD(MUL(i,w),31):ADD(MUL(i,w),34)] {BORDER} CPUT(board)
            // medio abajo
             [ADD(MUL(ADD(i,19),w),17):ADD(MUL(ADD(i,19),w),20)] {BORDER} CPUT(board)
             [ADD(MUL(ADD(i,19),w),31):ADD(MUL(ADD(i,19),w),34)] {BORDER} CPUT(board)
         NEXT
         EXIT
      }
      
      CASE(5){
         SET(i,3)
         PERF-UP(i,5,1)
            // laterales extremos:
             [ADD(MUL(i,w),5):ADD(MUL(i,w),8)]                  {BORDER} CPUT(board)
             [ADD(MUL(i,w),SUB(w,7)):ADD(MUL(i,w),SUB(w,4))]    {BORDER} CPUT(board)
            // laterales medios
             [ADD(MUL(ADD(i,9),w),5):ADD(MUL(ADD(i,9),w),8)]               {BORDER} CPUT(board)
             [ADD(MUL(ADD(i,9),w),SUB(w,7)):ADD(MUL(ADD(i,9),w),SUB(w,4))] {BORDER} CPUT(board)
            // laterales fondo
             [ADD(MUL(ADD(i,19),w),5):ADD(MUL(ADD(i,19),w),8)]               {BORDER} CPUT(board)
             [ADD(MUL(ADD(i,19),w),SUB(w,7)):ADD(MUL(ADD(i,19),w),SUB(w,4))] {BORDER} CPUT(board)
            // medio arriba
             [ADD(MUL(i,w),17):ADD(MUL(i,w),20)]      {BORDER} CPUT(board)
             [ADD(MUL(i,w),31):ADD(MUL(i,w),34)]      {BORDER} CPUT(board)
            // medio abajo
             [ADD(MUL(ADD(i,19),w),17):ADD(MUL(ADD(i,19),w),20)] {BORDER} CPUT(board)
             [ADD(MUL(ADD(i,19),w),31):ADD(MUL(ADD(i,19),w),34)] {BORDER} CPUT(board)
         NEXT
         EXIT
      }
      CASE(6){
         SET(i,1)
         PERF-UP(i,29,1)
            GOSUB(plant)
         NEXT
         LET(time out := TIME_CHALLENGE)      
      }
      
      CASE(7){
         SET(i,3)
         PERF-UP(i,5,1)
            // laterales extremos:
             [ADD(MUL(i,w),5):ADD(MUL(i,w),8)]               {BORDER} CPUT(board)
             [ADD(MUL(i,w),SUB(w,7)):ADD(MUL(i,w),SUB(w,4))] {BORDER} CPUT(board)
            // laterales medios
             [ADD(MUL(ADD(i,9),w),5):ADD(MUL(ADD(i,9),w),8)]               {BORDER} CPUT(board)
             [ADD(MUL(ADD(i,9),w),SUB(w,7)):ADD(MUL(ADD(i,9),w),SUB(w,4))] {BORDER} CPUT(board)
            // laterales fondo
             [ADD(MUL(ADD(i,19),w),5):ADD(MUL(ADD(i,19),w),8)]               {BORDER} CPUT(board)
             [ADD(MUL(ADD(i,19),w),SUB(w,7)):ADD(MUL(ADD(i,19),w),SUB(w,4))] {BORDER} CPUT(board)
            // medio arriba
             [ADD(MUL(i,w),17):ADD(MUL(i,w),20)] {BORDER} CPUT(board)
             [ADD(MUL(i,w),31):ADD(MUL(i,w),34)] {BORDER} CPUT(board)
            // medio medio
             [ADD(MUL(ADD(i,9),w),17):ADD(MUL(ADD(i,9),w),20)] {BORDER} CPUT(board)
             [ADD(MUL(ADD(i,9),w),31):ADD(MUL(ADD(i,9),w),34)] {BORDER} CPUT(board)
            // medio abajo
             [ADD(MUL(ADD(i,19),w),17):ADD(MUL(ADD(i,19),w),20)] {BORDER} CPUT(board)
             [ADD(MUL(ADD(i,19),w),31):ADD(MUL(ADD(i,19),w),34)] {BORDER} CPUT(board)
         NEXT
         EXIT
      }
   SELEND
   
   COND( NOT-EQ?(LEVEL,3) )
      LET( nHead := MUL( w, SUB( SUB( h, 1 ), MOD(h,2) )) DIV-INTO(2) )
      LET( dir := N )
   ELS
      LET( nHead := ADD( MUL( w, 13 ), 26 ) )
      LET( dir := N )
   CEND
   [ nHead ] {initial pos} CPUT( board )

   IF( swPierde, SRAND( ~SECONDS); FALSE(swPierde); {TIME_LOST} »» (time out),\
                 SRAND( 26785 ) )

   GOSUB( plant )
RET

DEF-FUN( you lost ) 
   SET(i,1), SET(k,0), SET(n,1)
   FOR( LE?(i, h), ++i)
      SET(j,1)
      FOR( LE?(j, w), ++j)
         LET( k := [ n ] GET(board) )
         COND( IS-NEG?( k ))
             LOCATE(i,j)
             PRN("\033[38;15;3m\033[48;5;9m~\OFF")
         CEND
         ++n
      NEXT
   NEXT
   SYS("aplay fl/Snake_dolor.wav </dev/null >/dev/null 2>&1 &")
   
RET

DEF-FUN( show ) 

   SET(i,1)
   MSET(j, k)
   SET(n,1)
   LOCATE(1,1)
   FOR( LE?(i, h), ++i)
      SET(j,1),LOG-INV(tiles)
      FOR( LE?(j, w), ++j)
         LET( k := [ n ] GET(board) )
         COND( IS-NEG?( k ))
            COND( NOT-EQ?(n,nHead) )
                IF(GT?(k,-3),IF(tiles,{tile1},{tile2});{"\033[38;5;15m+\OFF"},"\033[38;5;6m\033[48;5;11m \OFF")
            ELS    
                COND( EQ?(dir,N))
                    IF(tiles,"\033[38;5;9m\033[48;5;15mv\OFF","\033[38;5;9m\033[48;5;15m|\OFF")

                ELS-COND( EQ?(dir,S))
                    IF(tiles,"\033[38;5;9m\033[48;5;15m|\OFF","\033[38;5;9m\033[48;5;15m^\OFF")

                ELS-COND( EQ?(dir,E))
                    IF(tiles,"\033[38;5;9m\033[48;5;15m<\OFF","\033[38;5;9m\033[48;5;15m-\OFF")

                ELS
                    IF(tiles,"\033[38;5;9m\033[48;5;15m-\OFF","\033[38;5;9m\033[48;5;15m>\OFF")

                CEND
            CEND
            
         ELS-COND( {k} IS-EQ?(BORDER))
            {"\033[38;5;82m\033[48;5;57m \OFF"}

         ELS-COND( {k}IS-EQ?(FOOD) )
            COLOR-FG(color food ),IF(tiles,{tile1},{tile2}) //back food)
            {"@\OFF"}
         ELS
            IF(tiles,{tile1},{tile2})
            {" \OFF"}
         CEND
         PRN
         LOG-INV(tiles)
         ++n
      NEXT
      PRNL
   NEXT
   color food+=2, 
   back food-=2, WHEN( EQ?(color food, PLUS-ONE(BACK_FOOD))){
                     LET( color food:=COLOR_FOOD)
                     LET( back food:=BACK_FOOD)
                 }
RET

// negative values denote the snake (a negated time-to-live in given cell)
 
// reduce a time-to-live, effectively erasing the tail
DEF-FUN( age )
   
   MSET( r, jAge, jR )
   CART( IS-NEG?( board ) ) »» (r), SET-RANGE(r)

   GET( board ) PLUS(1) »» (jAge)
  
  // this is necessary, because Hopper arrays begining in 1
   CART( IS-ZERO?(jAge) ) »» (jR)
   COND( IS-ARRAY?(jR) )
      SET-RANGE(jR), SET(jAge, 1), SET-RANGE(r) 
   CEND
  // ******
   {jAge} PUT(board), CLR-RANGE

RET

DEF-FUN( step )
   LET( len := [nHead] GET(board) )
   SELECT(dir)
      CASE (N){ nHead -= w, EXIT }
      CASE (S){ nHead += w, EXIT }
      CASE (W){ --nHead, EXIT }
      CASE (E){ ++nHead, EXIT }
   SELEND
   SELECT( [nHead]CGET(board))
      CASE (SPACE){
          --len, LET( len := IF( EQ?(len,0), 1, len) )
          [nHead] { len }, CPUT(board) // keep in mind len is negative
          GOSUB( age )
          EXIT
      }
      CASE (FOOD){
          COND( NOT(OR(EQ?(LEVEL,3),EQ?(LEVEL,6))))
             --len, LET( len := IF( IS-ZERO?(len), 1, len) )
          ELS
             ++len  // quita celda del cuerpo: suma vida!
          CEND
          [nHead] { len }, CPUT(board)
          WHEN(AND(NOT-EQ?(LEVEL,3),NOT-EQ?(LEVEL,6))){ GOSUB( plant ) }
          ADD(SCORE,TIME), MOVE-TO(SCORE)

          {SCORE, 4, COLUMNA_PRINT} GOSUB( put score )
          LET( TIME := 100 )
          ++counter, COND( EQ?( counter, 25 ) )
                        LET( TLimit := SUB( TLimit,5 ))
                        CLAMP(INF_TIME, LIMIT_TIME, TLimit)
                        LET( counter := 0 )
                     CEND
          WHEN( OR(EQ?(LEVEL,3),EQ?(LEVEL,6))){ ++Consumed stage }
          ++ConsPrey
          COLOR-FG(10)
          LOCATE(20,COLUMNA_PRINT) PRN("SPEED: ")
          LOC-ROW(21),  PRN( ROUND(MUL(INV(TLimit),LIMIT_TIME),2), " M/S     " )
                        
          LOC-ROW(23)   PRN("CONSUMED PREY:")
          LOC-ROW(24)   PRN(ConsPrey,"\OFF")
          
          LET( color food:=COLOR_FOOD)
          LET( back food:=BACK_FOOD)
 
          SYS("aplay fl/Snake_mascada.wav </dev/null >/dev/null 2>&1 &")
          
          TIC( T ),{TIME, 12, COLUMNA_PRINT} GOSUB( put time )

          EXIT
      }
      CASE-NEGATIVE {
          GOSUB(KILLSOUND)
          SYS("aplay fl/Snake_mascada.wav </dev/null >/dev/null 2>&1")
          LET( quit := 1 )
          EXIT
      }  
      DEFAULT {
          GOSUB(KILLSOUND)
          SYS("aplay fl/Snake_golpe.wav </dev/null >/dev/null 2>&1")

          LET( quit := 1 )
      }
   SELEND
RET

DEF-FUN( LEVELCLEAR )
   GOSUB(KILLSOUND)
   GOSUB( level clear )
   SYS("aplay fl/Snake_level_clear.wav </dev/null >/dev/null 2>&1")
   SLEEP(1)
   
   WHEN( OR( EQ?(LEVEL,3), EQ?(LEVEL,6))){
      WHEN( EQ?(Consumed stage,20)){
          GOSUB( show )
          SYS( "aplay fl/Snake_bono.wav </dev/null >/dev/null 2>&1 &" )
          GOSUB(GANA BONO),SLEEP(3)
          ADD(SCORE,1000), MOVE-TO(SCORE)
          {SCORE, 4, COLUMNA_PRINT} GOSUB( put score )
      }
   }
   GOSUB( another play )
RET

DEF-FUN( another play )
   ++sound index, WHEN( EQ?(sound index,6)){ LET(sound index := 2) }
   back tiles+=2, WHEN( EQ?(back tiles,15)){ LET(back tiles:=1) }

   [back tiles] GET(head back tiles), MOVE-TO(tile1)
   [PLUS-ONE(back tiles)] GET(head back tiles), MOVE-TO(tile2)
   
   ++LEVEL, WHEN( GT?(LEVEL,7)){ LET(LEVEL:=1) }
   
   ++ REAL LEVEL
   COLOR-FG(15),LOCATE(26,COLUMNA_PRINT) SET-ITALIC, PRN("LEVEL: ",REAL LEVEL,"\OFF")

   LET( len := [nHead] GET(board) )
   [1:Size] MEM(1) CPUT(board)
   CLR-INTERVAL
   MEM(len), GOSUB( start )
   
   GOSUB( show )
   COND( OR( EQ?(LEVEL,3), EQ?(LEVEL,6)))
      GOSUB(challenge stage)
   ELS
      GOSUB( ready )
   CEND
   SLEEP(2)
   SYS( CAT(CAT("aplay fl/",[sound index]CGET(sounds)), "</dev/null >/dev/null 2>&1 &"))
   LET( TIME := TOTAL_TIME )
   {TIME, 12, COLUMNA_PRINT} GOSUB( put time )
RET

// put a piece of food at random empty position
DEF-FUN( plant )
   SET(r, 0)
   LOOP( search position )
      LET( r := MOD( CEIL(RAND(MINUS-ONE(Size))), Size ) )
   BACK-IF-NOT-EQ( [r] GET(board), SPACE, search position)
   [r] {FOOD} CPUT( board )
RET

DEF-FUN( put titles )
   LOCATE(2,COLUMNA_PRINT) PRN("\033[38;5;15mSCORE\OFF")
   {SCORE, 4, COLUMNA_PRINT} GOSUB( put score )
   LOCATE(10,COLUMNA_PRINT) PRN("\033[38;5;11mTIME\OFF")
   {TIME, 12, COLUMNA_PRINT} GOSUB( put time )
   
   COLOR-FG(15)
   LOCATE(26,COLUMNA_PRINT) SET-ITALIC,  PRN("LEVEL: ",REAL LEVEL,"\OFF")
   LOCATE(27,COLUMNA_PRINT) SET-INVERSE, PRN("LIVES: ",lives,"\OFF")

RET

DEF-FUN( put time, B, posx, posy )
   MSET( i,j,x )
   MSET( sb, lsb,nB, p4 )
   SET( k,1 )

   LOCATE (posx, posy) FILL-BOX(" ",5,20)
   LET( sb := STR(B) )
   LET( lsb := LEN(sb) )
   SET( rx, posy )
   LET( p4 := ADD( posx, 4 ))
   
   COLOR-FG(11)
   PERF-UP(k, lsb, 1) 
      LET( nB := VAL( MID( 1, k, sb )) ) 
      SET(x, 1), SET( i, posx )
      FOR( LE?(i, p4), ++i )
         SET( j, rx )
         FOR( LE?(j, ADD( rx, 2 ) ), ++j )
            LOCATE(i, j) PRN( STR-TO-UTF8(CHAR( [ PLUS-ONE(nB), x] CGET(big number) MUL-BY(219) )))
            ++x
         NEXT
       NEXT
       rx += 4
   NEXT
   PRN("\OFF")
RET

DEF-FUN( put score, SCORE, posx, posy )
    MSET( ln,s, sp )

    LET( sp := STR( SCORE ))
    LET( ln := LEN(sp))

    LOCATE ( posx, posy ) FILL-BOX(" ",4,20)
    SET(i, 1)
    COLOR-FG(15)
    PERF-UP( i, ln, 1)
       LET( s := VAL( MID( 1, i, sp )) )
       [ PLUS-ONE(s) ]
       LOCATE( posx, posy ),      PRN ( STR-TO-UTF8( GET(numL1) ))
       LOC-ROW( PLUS-ONE(posx) ), PRN ( STR-TO-UTF8( GET(numL2) ))
       LOC-ROW( PLUS-TWO(posx) ), PRN ( STR-TO-UTF8( GET(numL3) ))
       posy += 2
    NEXT
    PRN("\OFF")
    COND( swExtra1 )
        COND( GE?(SCORE,5000) )
           ++lives
           FALSE(swExtra1)
           SYS( "aplay fl/Snake_bono.wav </dev/null >/dev/null 2>&1 &" )
           LOCATE(27,COLUMNA_PRINT) SET-INVERSE, PRN("LIVES: ",lives,"\OFF")
        CEND
    ELS-COND(swExtra2)
        COND( GE?(SCORE,10000) )
           ++lives
           FALSE(swExtra2)
           SYS( "aplay fl/Snake_bono.wav </dev/null >/dev/null 2>&1 &" )
           LOCATE(27,COLUMNA_PRINT) SET-INVERSE, PRN("LIVES: ",lives,"\OFF")
        CEND
    CEND
RET

DEF-FUN( set score )

   {"┌┐"," ┐","┌┐","┌┐","┐┐","┌┐","┌┐","┌┐","┌┐","┌┐"} APND-LST(numL1)
   {"││"," │","┌┘"," ┤","└┤","└┐","├┐"," │","├┤","└┤"} APND-LST(numL2)
   {"└┘"," ┴","└┘","└┘"," ┘","└┘","└┘"," ┴","└┘","└┘"} APND-LST(numL3)

   {1,1,1,1,0,1,1,0,1,1,0,1,1,1,1} APND-ROW( big number )
   {1,1,0,0,1,0,0,1,0,0,1,0,1,1,1} APND-ROW( big number )
   {1,1,1,0,0,1,1,1,1,1,0,0,1,1,1} APND-ROW( big number )
   {1,1,1,0,0,1,0,1,1,0,0,1,1,1,1} APND-ROW( big number )
   {1,0,1,1,0,1,1,1,1,0,0,1,0,0,1} APND-ROW( big number )
   {1,1,1,1,0,0,1,1,1,0,0,1,1,1,1} APND-ROW( big number )
   {1,0,0,1,0,0,1,1,1,1,0,1,1,1,1} APND-ROW( big number )
   {1,1,1,0,0,1,0,0,1,0,0,1,0,0,1} APND-ROW( big number )
   {1,1,1,1,0,1,1,1,1,1,0,1,1,1,1} APND-ROW( big number )
   {1,1,1,1,0,1,1,1,1,0,0,1,0,0,1} APND-ROW( big number )

RET

DEF-FUN( ready )
   {"\033[38;5;4m\033[48;5;11m"}
   LOC-COL(16)
   LOC-ROW(13); PRN( STR-TO-UTF8("  ▄   ▄▄  ▄  ▄▄  ▄ ▄ "))
   LOC-ROW(14); PRN( STR-TO-UTF8(" █▄▀ █▀  █▄█ █ █ ▀▄▀ "))
   LOC-ROW(15); PRN( STR-TO-UTF8(" ▀ ▀  ▀▀ ▀ ▀ ▀▄▀  ▀  "))
   PRN("\OFF")
RET

DEF-FUN( level clear )
   {"\033[38;5;4m\033[48;5;11m"}
   LOC-COL(17) 
   LOC-ROW(12); PRN( STR-TO-UTF8( " ▄   ▄▄ ▄ ▄  ▄▄ ▄   ")) 
   LOC-ROW(13); PRN( STR-TO-UTF8( " █  █▀  █ █ █▀  █   "))  
   LOC-ROW(14); PRN( STR-TO-UTF8( "  ▀  ▀▀  ▀   ▀▀  ▀  "))
   LOC-ROW(15); PRN( STR-TO-UTF8( "  ▄ ▄   ▄▄  ▄   ▄   "))
   LOC-ROW(16); PRN( STR-TO-UTF8( " █  █  █▀  █▄█ █▄▀  "))
   LOC-ROW(17); PRN( STR-TO-UTF8( "  ▀  ▀  ▀▀ ▀ ▀ ▀ ▀  "))
   PRN("\OFF")
RET

DEF-FUN( challenge stage )
   {"\033[38;5;4m\033[48;5;11m"}
   LOC-COL(9) 
   LOC-ROW(12); PRN( STR-TO-UTF8( "  ▄ ▄ ▄  ▄  ▄  ▄   ▄▄  ▄   ▄▄  ▄▄ "))
   LOC-ROW(13); PRN( STR-TO-UTF8( " █  █▄█ █▄█ █  █  █▀  █ █ █ ▄ █▀  "))
   LOC-ROW(14); PRN( STR-TO-UTF8( "  ▀ ▀ ▀ ▀ ▀  ▀  ▀  ▀▀ ▀ ▀  ▀▀  ▀▀ "))
   LOC-ROW(15); PRN( STR-TO-UTF8( "         ▄ ▄ ▄  ▄   ▄▄  ▄▄        "))
   LOC-ROW(16); PRN( STR-TO-UTF8( "        ▀▄  █  █▄█ █ ▄ █▀         "))
   LOC-ROW(17); PRN( STR-TO-UTF8( "        ▀   ▀  ▀ ▀  ▀▀  ▀▀        "))
   PRN("\OFF")
RET

DEF-FUN( GANA BONO )
   {"\033[38;5;11m\033[48;5;196m"}
   LOC-COL(17)
   LOC-ROW(12); PRN( STR-TO-UTF8("  ▄   ▄   ▄  ▄ ▄  ▄ "))
   LOC-ROW(13); PRN( STR-TO-UTF8(" █▄▀ █ █ █ █ █ █ ▀▄ "))
   LOC-ROW(14); PRN( STR-TO-UTF8(" ▀▄▀  ▀  ▀ ▀  ▀  ▀  "))
   LOC-ROW(15); PRN( STR-TO-UTF8("  ▄     ▄   ▄   ▄   "))
   LOC-ROW(16); PRN( STR-TO-UTF8("   █   █ █ █ █ █ █  "))
   LOC-ROW(17); PRN( STR-TO-UTF8("   █ ▄ ▀▄▀ ▀▄▀ ▀▄▀  "))
   PRN("\OFF")   
RET

DEF-FUN( game over )
    {"\033[38;5;15m\033[48;5;9m"}
    LOC-COL(17)
    LOC-ROW(12); PRN( STR-TO-UTF8("  ▄▄  ▄   ▄ ▄   ▄▄ "))
    LOC-ROW(13); PRN( STR-TO-UTF8(" █ ▄ █▄█ █ █ █ █▀  "))
    LOC-ROW(14); PRN( STR-TO-UTF8("  ▀▀ ▀ ▀ ▀ ▀ ▀  ▀▀ "))
    LOC-ROW(15); PRN( STR-TO-UTF8("   ▄  ▄ ▄  ▄▄  ▄   "))
    LOC-ROW(16); PRN( STR-TO-UTF8("  █ █ █ █ █▀  █▄▀  "))
    LOC-ROW(17); PRN( STR-TO-UTF8("   ▀   ▀   ▀▀ ▀ ▀  "))
    PRN("\OFF")
RET

DEF-FUN( titles )
    #define COLOR_INI   232
    #define COLOR_FIN   255
    SET(k,1)
    PERF-UP(k,2,1)
       SET(j,COLOR_INI), SET(jbg,COLOR_FIN)
       PERF-UP(j,COLOR_FIN,1)
          COLOR(j, jbg--)
          LOC-COL(17)
          LOC-ROW(12); PRN(STR-TO-UTF8("                      "))
          LOC-ROW(13); PRN(STR-TO-UTF8("   ▄  ▄   ▄  ▄ ▄  ▄▄  "))
          LOC-ROW(14); PRN(STR-TO-UTF8("  ▀▄ █ █ █▄█ █▄  █▀   "))
          LOC-ROW(15); PRN(STR-TO-UTF8("  ▀  ▀ ▀ ▀ ▀ ▀ ▀  ▀▀  "))
          LOC-ROW(16); PRN(STR-TO-UTF8("   ▄  ▄   ▄▄  ▄▄ ▄▄   "))
          LOC-ROW(17); PRN(STR-TO-UTF8("  ▀▄ █▄█ █▀  █▀  █ █  "))
          LOC-ROW(18); PRN(STR-TO-UTF8("  ▀  ▀    ▀▀  ▀▀ ▀▄▀  "))
          LOC-ROW(19); PRN(STR-TO-UTF8("                      "))
          MICROSECS(20000)
       NEXT
       SET(j,COLOR_FIN), SET(jbg,COLOR_INI)
       PERF-DOWN(j,COLOR_INI,1)
          COLOR(j, jbg++)
          LOC-COL(17)
          LOC-ROW(12); PRN(STR-TO-UTF8("                      "))
          LOC-ROW(13); PRN(STR-TO-UTF8("   ▄  ▄   ▄  ▄ ▄  ▄▄  "))
          LOC-ROW(14); PRN(STR-TO-UTF8("  ▀▄ █ █ █▄█ █▄  █▀   "))
          LOC-ROW(15); PRN(STR-TO-UTF8("  ▀  ▀ ▀ ▀ ▀ ▀ ▀  ▀▀  "))
          LOC-ROW(16); PRN(STR-TO-UTF8("   ▄  ▄   ▄▄  ▄▄ ▄▄   "))
          LOC-ROW(17); PRN(STR-TO-UTF8("  ▀▄ █▄█ █▀  █▀  █ █  "))
          LOC-ROW(18); PRN(STR-TO-UTF8("  ▀  ▀    ▀▀  ▀▀ ▀▄▀  "))
          LOC-ROW(19); PRN(STR-TO-UTF8("                      "))
          MICROSECS(20000)
       NEXT
    NEXT
    PRN("\OFF")
RET

DEF-FUN( prepare maze )
    REPL(12,ADD(MUL(w, 3),20),"333333333333",Maze),   MOVE-TO(Maze)
    REPL(12,ADD(MUL(w, 24),20),"333333333333",Maze),  MOVE-TO(Maze)
    
    REPL(12,ADD(MUL(w, 6),7),"333333333333",Maze),    MOVE-TO(Maze)
    REPL(12,ADD(MUL(w, 6),33),"333333333333",Maze),   MOVE-TO(Maze)

    REPL(12,ADD(MUL(w, 21),7),"333333333333",Maze),   MOVE-TO(Maze)
    REPL(12,ADD(MUL(w, 21),33),"333333333333",Maze),  MOVE-TO(Maze)
    
    SET(i,7)
    PERF-UP(i,10,1)
       REPL(1,ADD(MUL(w, i),7),"3",Maze),           MOVE-TO(Maze) 
       REPL(1,ADD(MUL(w, i),44),"3",Maze),          MOVE-TO(Maze)
       REPL(1,ADD(MUL(w, ADD(i,10)),44),"3",Maze),  MOVE-TO(Maze) 
       REPL(1,ADD(MUL(w, ADD(i,10)),7),"3",Maze),   MOVE-TO(Maze) 
    NEXT
RET
Output:

AutoHotkey

gosub Init
Gui, +AlwaysOnTop
Gui, font, s12, consolas
Gui, add, Edit, vEditGrid x10 y10 ReadOnly, % grid2Text(oGrid) 
Gui, add, Text, vScore x10 y+10 w200 ReadOnly, % "Your Score = " Score
Gui, show,, Snake
GuiControl, Focus, Score
return
;-----------------------------------------
Init:
Width := 100, Height := 30			; set grid size
tail := 20, step := 10
Timer := 75
item := 0, direction := ""
oTrail := [], Score := 0
xx := generateGrid(width, Height)
oGrid := xx.1
row := xx.2, col := xx.3
return
;-----------------------------------------
Move:
if !item
{
	loop
	{
		Random, itemC, 3, % width-2
		Random, itemR, 3, % Height-2
	}
	until, !oGrid[itemR, itemC]
	oGrid[itemR, itemC] := "@"
	item := true
}
gosub, crawl
return
;-----------------------------------------
#IfWinActive, Snake
left::
Right::
up::
Down::
if ((A_ThisHotkey = "right") 	&& (Direction = "left"))
|| ((A_ThisHotkey = "left") 	&& (Direction = "right"))
|| ((A_ThisHotkey = "up") 		&& (Direction = "Down"))
|| ((A_ThisHotkey = "Down") 	&& (Direction = "up"))
|| ((A_ThisHotkey = "right") 	&& (Direction = "right"))
|| ((A_ThisHotkey = "left") 	&& (Direction = "left"))
|| ((A_ThisHotkey = "up") 		&& (Direction = "up"))
|| ((A_ThisHotkey = "Down") 	&& (Direction = "Down"))
	return

Direction := A_ThisHotkey
gosub, crawl
return
#IfWinActive
;-----------------------------------------
crawl:
switch, Direction	
{
	case "left"	:	oGrid[row  , col--] := 1
	case "Right"	:	oGrid[row  , col++] := 1
	case "up" 	:	oGrid[row--, col  ] := 1
	case "Down"	:	oGrid[row++, col  ] := 1
}

; out of bounds or snake eats itself
if oGrid[row, col] = 1 || col < 1 || col > width || row < 1 || row > height
	gosub, YouLose

; snake eats item
if (oGrid[row, col] = "@")
{
	item := false
	tail += step
	GuiControl,, Score, % "Your Score = " ++Score
}

; snake advances
oGrid[row, col] := 2
oTrail.Push(row "," col)
if (oTrail.count() >= tail)
	x := StrSplit(oTrail.RemoveAt(1), ","),		oGrid[x.1, x.2] := false

GuiControl,, EditGrid, % grid2Text(oGrid)
SetTimer, Move, % 0-Timer
return
;-----------------------------------------
YouLose:
SetTimer, Move, Off
MsgBox, 262180, ,% "Your Score is " Score "`nPlay Again?"
IfMsgBox, No
	ExitApp

gosub Init
GuiControl,, EditGrid, % grid2Text(oGrid)
return
;-----------------------------------------
grid2Text(oGrid){
	for row, obj in oGrid	{
		for col, val in obj		; @=item, 2=Head, 1=tail
			text .=  val = "@" ? "@" : val =2 ? "█" : val = 1 ? "▓" : " " 
		text .= "`n"
	}
	return trim(text, "`n")
}
;-----------------------------------------
generateGrid(width, Height){
	global oTrail
	oGrid := []
	loop, % width
	{
		col := A_Index
		loop, % Height
			row := A_Index, oGrid[row, col] := false
	}
	Random, col, 3, % width-2
	Random, row, 3, % Height-2
	oGrid[row, col] := 2
	oTrail.Push(row "," col)
	return [oGrid, row, col]
}
;-----------------------------------------

BASIC

Applesoft BASIC

Rather than using DIM X%(1599),Y%(1599) which takes a long time to initialize, instead to speed this up, the program will POKE and PEEK the snake coordinates in memory. On initialization, LOMEM: 24576 moves variables out of the way.

Continue playing by typing the CONT command from the Applesoft BASIC prompt if you quit or it's game over.

REM === VARIABLES ===
REM
REM D(4)   X DELTAS
REM E(4)   Y DELTAS
REM K(255) KEYCODE BEHESTS
REM FN X   X COORDINATES OF SNAKE FROM MEMORY
REM FN Y   Y COORDINATES OF SNAKE FROM MEMORY
REM A      <UNUSED>
REM B      BEHEST: 0=NOTHING 1=RIGHT 2=LEFT 3=DOWN 4=UP 5=QUIT
REM C      SCRN COLOR
REM D      X-DELTA
REM E      Y-DELTA
REM F      FOOD COLOR= 4 DARK GREEN
REM H      HIT= 127
REM I      KEYBOARD= 49152
REM J      CLEAR KEYBOARD= 49168
REM K      KEYCODE
REM L      LENGTH WIDTH&HEIGHT= 40
REM M      MAX= 1599 UPPER BOUND OF SNAKE X AND Y COORDINATES
REM N      NEW POSITION OF HEAD OF SNAKE
REM O      OLD POSITION OF TAIL OF SNAKE
REM P      ADDRESS OF 1600 X COORDINATES OF SNAKE
REM Q      QUIT= 5
REM R      REMAINING COLOR= 9 ORANGE
REM S      SNAKE HEAD COLOR= 13 YELLOW
REM T      ONE= 1
REM U      X FOOD COORDINATE
REM V      Y FOOD COORDINATE
REM W      WALL COLOR= 10 GREY
REM X      X SNAKE HEAD COORDINATE
REM Y      Y SNAKE HEAD COORDINATE
REM Z      ADDRESS OF 1600 Y COORDINATES OF SNAKE
REM Q$     QUIT MESSAGE

REM === KEYBOARD ===
REM
REM UPPER  LOWER KEY BEHEST
REM  155         ESC  QUIT
REM  139         UP   UP
REM  193    225   A   UP
REM  201    233   I   UP
REM  138         DOWN DOWN
REM  218    250   Z   DOWN
REM  203    235   K   DOWN
REM  136         LEFT LEFT
REM  202    234   J   LEFT
REM  149        RIGHT RIGHT
REM  204    236   L   RIGHT

 0  ON B = Q GOTO 5: IF B THEN D = D(B):E = E(B)
 1 X = X + D:Y = Y + E:C =  SCRN( X,Y): COLOR= S: PLOT X,Y: COLOR= R: PLOT  FN X(N), FN Y(N):N = N - T + (N = 0) * M: POKE P + N,X: POKE Z + N,Y: ON C(C) GOTO 4: IF  NOT C THEN  COLOR= 0: PLOT  FN X(O), FN Y(O):O = O - T + (O = 0) * M
 2  IF C THEN U =  INT ( RND (T) * L):V =  INT ( RND (T) * L): ON  SCRN( U,V) > 0 GOTO 2: COLOR= F: PLOT U,V
 3 K =  PEEK (I):B =  PEEK (J * (K > H)):B = K(K): GOTO
 4  COLOR= T: PLOT X,Y: READ Q$: DATA5,20,20,1,-1,1,-1,13,9,4,10,1,40,1599,127,49152,49168,4608,6400,5,4,4,4,4,4,3,3,3,3,3,2,2,2,1,1,1,DONE,GAME OVER
 5  IF Q = 5 THEN  PRINT Q$;: END : HOME :Q = Q * (B = Q):B = 0: ON Q = 5 GOTO : RUN
 6  LOMEM: 24576
 7  DIM K(255),D(4),E(4),C(15): READ Q,X,Y,D(1),D(2),E(3),E(4),S,R,F,W,T,L,M,H,I,J,P,Z,K(155),K(139),K(193),K(225),K(201),K(233),K(138),K(218),K(250),K(203),K(235),K(136),K(202),K(234),K(149),K(204),K(236),Q$
 8  DEF  FN X(I) =  PEEK (P + I): DEF  FN Y(I) =  PEEK (Z + I):B =  RND (0):C(S) = T:C(R) = T:C(W) = T:B =  INT ( RND (T) * 4) + T:D = D(B):E = E(B): POKE P + O,X: POKE Z + O,Y
 9  GR : HOME : COLOR= W: VLIN 0,39 AT 0: VLIN 0,39 AT 39: HLIN 1,38 AT 0: HLIN 1,38 AT 39: COLOR= F: PLOT X + D,Y + E: GOTO

Craft Basic

rem Snake example game for Craft Basic
rem by Gemino Smothers 2022
rem www.lucidapogee.com

title "Snake!"

define gfxx = 330, gfxy = 296
define upkey = 0, rightkey = 0, downkey = 0, leftkey = 0, esckey = 0
define speed = 0, delay = 0, score = 0, game = 1
define maxsize = 1000, size = 9, direction = int(rnd * 4) + 1
define rx = int(rnd * (gfxx - 24)) + 12
define ry = int(rnd * (gfxy - 40)) + 25

dim sx[maxsize]
dim sy[maxsize]

let sx[0] = gfxx / 2
let sy[0] = gfxy / 2

fill on
bgcolor 128, 64, 0
cls graphics

resize 0, 0, gfxx + 10, gfxy + 56
center

formid 1
staticform 1, 1, 100, 14
fgcolor 255, 255, 0
bgcolor 0, 80, 0
colorform

alert "Snake! by Gemino Smothers 2022"
alert "Get the gray little rodents and avoid hitting yourself or walls."
alert "Use arrow keys to move. Esc to exit."

input "Enter game speed between 0 to 100+", speed

fgcolor 0, 80, 0
rect 0, 0, gfxx, gfxy

do

	button upkey, 38
	button rightkey, 39
	button downkey, 40
	button leftkey, 37
	button esckey, 27

	if upkey = 1 and direction <> 3 then

		let direction = 1

	endif

	if rightkey = 1 and direction <> 4 then

		let direction = 2

	endif

	if downkey = 1 and direction <> 1 then

		let direction = 3

	endif

	if leftkey = 1 and direction <> 2 then

		let direction = 4

	endif

	fgcolor 0, 80, 0
	oval sx[size], sy[size], 15, 15

	let i = size + 1

	do

		let i = i - 1
		let c = i - 1

		if sx[0] = sx[i] and sy[0] = sy[i] = 1 then

			let game = 0

		endif

		let sx[i] = sx[c]
		let sy[i] = sy[c]

		fgcolor 0, 255, 0
		oval sx[i], sy[i], 15, 15

	loop i > 1

	fgcolor 0, 0, 255
	oval sx[0] + 5, sy[0] + 5, 3, 3
	oval sx[0] + 9, sy[0] + 5, 3, 3

	if direction = 1 then

		let sy[0] = sy[0] - 15

	endif

	if direction = 2 then

		let sx[0] = sx[0] + 15

	endif

	if direction = 3 then

		let sy[0] = sy[0] + 15

	endif

	if direction = 4 then

		let sx[0] = sx[0] - 15

	endif

	if sx[0] <= -10 or sx[0] >= gfxx or sy[0] <= -10 or sy[0] >= gfxy = 1 then

		let game = 0

	endif

	if sx[0] + 15 >= rx and sx[0] <= rx + 15 and sy[0] + 15 >= ry and sy[0] <= ry + 15 = 1 then

		playwave "examples\tada.wav"

		fgcolor 0, 80, 0
		rect 0, 0, gfxx, gfxy

		let rx = int(rnd * (gfxx - 24)) + 12
		let ry = int(rnd * (gfxy - 40)) + 25

		let size = size + 3
		let score = score + 1

	endif

	fgcolor 100,100,100
	oval rx, ry, 15, 15

	fgcolor 255, 0, 0
	oval rx + 5, ry + 5, 3, 3
	oval rx + 9, ry + 5, 3, 3

	fgcolor 255, 255, 0
	formid 1
	formtext "Score: ", score
	updateform

	let delay = clock

	do

		wait

	loop clock < delay + speed

loop esckey <> 1 and game = 1

playwave "examples\boom.wav"
alert "Game over! Score: ", score

FreeBASIC

REM Snake

#define EXTCHAR Chr(255)

Dim Shared As Integer puntos, contar, longitud, posX, posY, oldhi = 0
Dim Shared As Integer x(500), y(500)
For contar = 1 To 500
    x(contar) = 0 : y(contar) = 0
Next contar
Dim Shared As Byte fruitX, fruitY, convida
Dim Shared As Single delay
Dim Shared As String direccion, usuario

Sub Intro
    Cls
    Color 15, 0
    Print "              _    _    _    _               "
    Print "             / \  / \  / \  / \              "
    Print " ___________/  _\/  _\/  _\/  _\____________ "
    Print " __________/  /_/  /_/  /_/  /______________ "
    Print "           | /\   /\   /\   / \ \___         "
    Print "           |/  \_/  \_/  \_/   \   "+Chr(248)+"\        "
    Print "                                \___/--<     "
    Color 14, 0
    Locate 10, 28: Print "---SNAKE---"
    Locate 12, 4: Print "Para jugar, usa las teclas de flecha para moverte."
    Locate 13, 4: Print "Pulsa <1-3> para velocidad, o <Esc> para salir."
    If puntos > oldhi Then oldhi = puntos
    Locate 14, 4: Print "M xima puntuaci¢n: "; oldhi
    usuario = ""
    While usuario = ""
        usuario = Inkey
    Wend
    If usuario = Chr(27) Then End 'ESC
    delay = .14
    If usuario = "1" Then delay = .26
    If usuario = "3" Then delay = .08
    Cls
    longitud = 9
    puntos = 0
    posX = 40 : posY = 10
    direccion = "derecha"
    convida = true
    fruitX = Int(Rnd * 79) + 1
    fruitY = Int(Rnd * 20) + 1
    Locate 22, 1
    For contar = 1 To 80
        Print Chr(196); ''"-";
    Next contar
End Sub

Sub MenuPrincipal
    Dim As Integer num, oldX, oldY
    Dim As Single tm, tm2
    
    Color 10
    Locate 23, 2: Print "SNAKE - <Esc> salir - Puntos: "; puntos
    If posX = fruitX And posY = fruitY Then
        longitud += 1
        puntos += 1
        fruitX = Int(Rnd * 79) + 1
        fruitY = Int(Rnd * 21) + 1
    End If
    Locate fruitY, fruitX : Color 12: Print Chr(01) : Color 10'"@"
    x(0) = posX : y(0) = posY
    
    For contar = 1 To 499
        num = 500 - contar
        x(num) = x(num - 1)
        y(num) = y(num - 1)
    Next contar
    
    oldX = x(longitud) : oldY = y(longitud)
    If oldX > 0 And oldY > 0 Then Locate oldY, oldX : Print " "
    
    Locate posY, posX: Print Chr(219) '"Û"
    tm = Timer
    tm2 = tm + delay
    While tm < tm2
        tm = Timer
        usuario = Inkey
        If usuario = EXTCHAR & "H" Then direccion = "arriba"
        If usuario = EXTCHAR & "P" Then direccion = "abajo"
        If usuario = EXTCHAR & "K" Then direccion = "izquierda"
        If usuario = EXTCHAR & "M" Then direccion = "derecha"
        If usuario = Chr(27) Then Intro
    Wend
    If direccion = "derecha" Then posX += 1
    If posX > 80 Then convida = false
    If direccion = "izquierda" Then posX -= 1
    If posX < 1 Then convida = false
    If direccion = "arriba" Then posY -= 1
    If posY < 1 Then convida = false
    If direccion = "abajo" Then posY += 1
    If posY > 21 Then convida = false
    
    For contar = 0 To longitud
        If posX = x(contar) And posY = y(contar) Then convida = false
    Next contar
    
    If convida = false Then
        Cls : Locate 11, 19: Print "Pulsa <space>..."
        Locate 10, 18: Print "Has muerto! Con"; puntos; " puntos." : Sleep
        While Inkey = "": Wend
        Intro
    End If
    MenuPrincipal
End Sub

'--- Programa Principal ---
Randomize Timer
Intro
MenuPrincipal
End
'--------------------------

Integer BASIC

Translation of: Applesoft BASIC
    0 IF B=Q THEN GOTO 9: IF B THEN D=D(B): IF B THEN E=E(B):X=X+D:Y=Y+E:A= SCRN(X,Y): COLOR=S: PLOT X,Y: COLOR=R: PLOT X(N),Y(N)
    1 N=N-T+(N=1)*M:X(N)=X:Y(N)=Y: IF C(A) THEN GOTO 9: IF A THEN GOTO 2: COLOR=0: PLOT X(O),Y(O):O=O-T+(O=1)*M: GOTO 3
    2 U= RND (L):V= RND (L): IF SCRN(U,V)>0 THEN GOTO 2: COLOR=F: PLOT U,V
    3 K= PEEK (I):B= PEEK (J*(K>H)):B=K(K): GOTO 0
    4 DIM X(1600),Y(1600),K(255),D(4),E(4),C(15): FOR I=0 TO 255:K(I)=0: NEXT I: FOR I=1 TO 4:D(I)=0:E(I)=0: NEXT I
    5 FOR I=0 TO 15:C(I)=0: NEXT I:Q=5:X=20:Y=20:E(1)=-1:D(2)=-1:E(3)=1:D(4)=1:S=13:R=9:F=4:W=10:T=1:L=40:M=1600
    6 H=127:I=-16384:J=-16368:K(155)=5:K(193)=1:K(225)=1:K(201)=1:K(233)=1:K(136)=2:K(202)=2:K(234)=2:N=1:O=1
    7 K(218)=3:K(250)=3:K(203)=3:K(235)=3:K(149)=4:K(204)=4:K(236)=4:C(S)=T:C(R)=T:C(W)=T:B= RND (4)+T:D=D(B)
    8 E=E(B):X(O)=X:Y(O)=Y: GR : CALL -936: COLOR=W: VLIN 0,39 AT 0: VLIN 0,39 AT 39: HLIN 1,38 AT 0: HLIN 1,38 AT 39: COLOR=F: PLOT X+D,Y+E: GOTO 0
    9 IF Q#5 THEN GOTO 4:Q=Q*(B=Q): COLOR=T: IF Q=0 THEN PLOT X,Y: IF Q=0 THEN PRINT "GAME OVER";: IF Q THEN PRINT "DONE";:K= PEEK (J):B=0: END

Locomotive Basic

Use the cursor keys to control movement direction. Lower the skill parameter in line 20 or increase ml (maximum length) if you find gameplay too easy.

If you are playing this in CPCBasic, first click on the CPC screen so it gets keyboard input and then enter "run" (clicking the Run button would deselect the screen again).

10 mode 1:randomize time
20 sx=20:sy=5:dx=1:dy=0:ml=20:dim ox(ml),oy(ml):oi=1:ll=4:skill=6
30 f$=chr$(228):w$=chr$(127):b$=chr$(231)
40 print string$(40,w$);
50 for i=2 to 20:print w$;space$(38);w$;:next
60 print string$(40,w$);
70 locate 10, 12:print string$(20,w$);
80 gosub 260
90 frame
100 if inkey(1)>-1 then dx=1:dy=0
110 if inkey(8)>-1 then dx=-1:dy=0
120 if inkey(0)>-1 then dx=0:dy=-1
130 if inkey(2)>-1 then dx=0:dy=1
140 locate sx,sy:print chr$(224);:ox(oi)=sx:oy(oi)=sy
150 oi=oi+1:if oi>ml then oi=1
160 nx=sx+dx:ny=sy+dy
170 locate nx,ny:a$=copychr$(#0)
180 if a$=w$ or a$=b$ then sound 2,62500/20,100:locate 13,6:print "You have died!":end
190 if a$=f$ then sound 2,62500/500,10: sound 1,62500/1000,10: sound 4,62500/2000,10:p=p+100:print " ";:gosub 260:if ll<ml then ll=ll+1
200 locate 1,24:print "SCORE:"p
210 for i=1 to skill:frame:next
220 locate sx,sy:print b$;
230 nn=1+((oi+ml-ll) mod ml)
240 if ox(nn)>0 then locate ox(nn),oy(nn):print " ";
250 sx=nx:sy=ny:goto 90
260 fx=rnd*39+1:fy=rnd*19+1
270 locate fx,fy:a$=copychr$(#0)
280 if a$<>" " then 260
290 print f$;
300 return

ZX Spectrum Basic

By ancient tradition, the controls are Q for up, A for down, O for left, and P for right.

A screenshot is here.

Note that lines 10 to 210 and 580 to 890—more than half the program—define graphics characters for the snake's head (facing in different directions) and for its food. If you're happy to make do with characters from the standard character set, you can easily adapt lines 220 to 570 to work on their own. The things the snake eats are supposed to be apples, although they don't look too much like them.

 10 FOR i=0 TO 7
 20 READ bits
 30 POKE USR "L"+i,bits
 40 NEXT i
 50 FOR i=0 TO 7
 60 READ bits
 70 POKE USR "R"+i,bits
 80 NEXT i
 90 FOR i=0 TO 7
100 READ bits
110 POKE USR "P"+i,bits
120 NEXT i
130 RESTORE 740
140 FOR i=7 TO 0 STEP -1
150 READ bits
160 POKE USR "D"+i,bits
170 NEXT i
180 FOR i=0 TO 7
190 READ bits
200 POKE USR "F"+i, bits
210 NEXT i
220 PAPER 0
230 CLS
240 LET snakex=19
250 LET snakey=15
260 LET dx=-1
270 LET dy=0
280 LET s$=CHR$ 15+CHR$ 20+CHR$ 15+CHR$ 21
290 LET foodx=INT (RND*32)
300 LET foody=INT (RND*22)
310 IF SCREEN$ (foody,foodx)<>" " THEN GO TO 290
320 INK 2
330 PRINT AT foody,foodx;CHR$ 149
340 INK 4
350 INVERSE 1
360 PRINT AT CODE s$,CODE s$(1);"#"
370 INVERSE 0
380 IF INKEY$="q" AND dy=0 THEN LET dx=0: LET dy=-1
390 IF INKEY$="a" AND dy=0 THEN LET dx=0: LET dy=1
400 IF INKEY$="o" AND dx=0 THEN LET dx=-1: LET dy=0
410 IF INKEY$="p" AND dx=0 THEN LET dx=1: LET dy=0
420 IF dx=-1 THEN PRINT AT snakey,snakex;CHR$ 155
430 IF dx=1 THEN PRINT AT snakey,snakex;CHR$ 161
440 IF dy=1 THEN PRINT AT snakey,snakex;CHR$ 159
450 IF dy=-1 THEN PRINT AT snakey,snakex;CHR$ 147
460 LET s$=CHR$ snakey+CHR$ snakex+s$
470 IF snakex=foodx AND snakey=foody THEN GO TO 290
480 PRINT AT CODE s$(LEN s$-1),CODE s$(LEN s$);" "
490 LET s$=s$( TO LEN s$-2)
500 LET snakex=snakex+dx
510 LET snakey=snakey+dy
520 IF snakex=-1 THEN LET snakex=31
530 IF snakex=32 THEN LET snakex=0
540 IF snakey=-1 THEN LET snakey=21
550 IF snakey=22 THEN LET snakey=0
560 IF SCREEN$ (snakey,snakex)="#" THEN STOP
570 GO TO 340
580 DATA BIN 00001111
590 DATA BIN 00111111
600 DATA BIN 01110011
610 DATA BIN 11110011
620 DATA BIN 11111111
630 DATA BIN 01111111
640 DATA BIN 00000111
650 DATA BIN 00011111
660 DATA BIN 11110000
670 DATA BIN 11111100
680 DATA BIN 11001110
690 DATA BIN 11001111
700 DATA BIN 11111111
710 DATA BIN 11111110
720 DATA BIN 11100000
730 DATA BIN 11111000
740 DATA BIN 00011000
750 DATA BIN 00111100
760 DATA BIN 01111100
770 DATA BIN 01111101
780 DATA BIN 11001101
790 DATA BIN 11001111
800 DATA BIN 11111111
810 DATA BIN 11111111
820 DATA BIN 00000100
830 DATA BIN 00001000
840 DATA BIN 01101011
850 DATA BIN 11111100
860 DATA BIN 11111100
870 DATA BIN 11111100
880 DATA BIN 01111111
890 DATA BIN 00110110

C

As some implementation below (C++) works on Windows console, let it work on Linux. Other implementations could be added as well, reusing the api.

// Snake

// The problem with implementing this task in C is, the language standard
// does not cover some details essential for interactive games:
// a nonblocking keyboard input, a positional console output,
// a and millisecond-precision timer: these things are all system-dependent.

// Therefore the program is split in two pieces, a system-independent
// game logic, and a system-dependent UI, separated by a tiny API:
char nonblocking_getch();
void positional_putch(int x, int y, char ch);
void millisecond_sleep(int n);
void init_screen();
void update_screen();
void close_screen();

// The implementation of a system-dependent part.
// Requires POSIX IEEE 1003.1-2008 compliant system and ncurses library.
#ifdef __linux__
#define _POSIX_C_SOURCE 200809L
#include <time.h> // nanosleep
#include <ncurses.h> // getch, mvaddch, and others
char nonblocking_getch() { return getch(); }
void positional_putch(int x, int y, char ch) { mvaddch(x, y, ch); }
void millisecond_sleep(int n) { 
	struct timespec t = { 0, n * 1000000 };
	nanosleep(&t, 0);
	// for older POSIX standards, consider usleep()
}
void update_screen() { refresh(); }
void init_screen() {
	initscr();
	noecho();
	cbreak();
	nodelay(stdscr, TRUE);
}
void close_screen() { endwin(); }
#endif

// An implementation for some other system...
#ifdef _WIN32
#error "not implemented"
#endif

// The game logic, system-independent
#include <time.h> // time
#include <stdlib.h> // rand, srand

#define w 80
#define h 40

int board[w * h];
int head;
enum Dir { N, E, S, W } dir;
int quit;

enum State { SPACE=0, FOOD=1, BORDER=2 };
// negative values denote the snake (a negated time-to-live in given cell)

// reduce a time-to-live, effectively erasing the tail
void age() {
        int i;
	for(i = 0; i < w * h; ++i)
		if(board[i] < 0)
			++board[i];
}

// put a piece of food at random empty position
void plant() {
	int r;
	do
		r = rand() % (w * h);
	while(board[r] != SPACE);
	board[r] = FOOD;
}

// initialize the board, plant a very first food item
void start(void) {
        int i;
	for(i = 0; i < w; ++i)
		board[i] = board[i + (h - 1) * w] = BORDER;
	for(i = 0; i < h; ++i)
		board[i * w] = board[i * w + w - 1] = BORDER;
	head = w * (h - 1 - h % 2) / 2; // screen center for any h
	board[head] = -5;
	dir = N;
	quit = 0;
	srand(time(0));
	plant();
}

void step() {
	int len = board[head];
	switch(dir) {
		case N: head -= w; break;
		case S: head += w; break;
		case W: --head; break;
		case E: ++head; break;
	}
	switch(board[head]) {
		case SPACE:
			board[head] = len - 1; // keep in mind len is negative
			age();
			break;
		case FOOD:
			board[head] = len - 1;
			plant();
			break;
		default:
			quit = 1;
	}
}

void show() {
	const char * symbol = " @.";
        int i;
	for(i = 0; i < w * h; ++i)
		positional_putch(i / w, i % w,
			board[i] < 0 ? '#' : symbol[board[i]]);
	update_screen();
}

int main (int argc, char * argv[]) {
	init_screen();
	start();
	do {
		show();
		switch(nonblocking_getch()) {
			case 'i': dir = N; break;
			case 'j': dir = W; break;
			case 'k': dir = S; break;
			case 'l': dir = E; break;
			case 'q': quit = 1; break;
		}
		step();
		millisecond_sleep(100); // beware, this approach
		// is not suitable for anything but toy projects like this
	}
	while(!quit);
	millisecond_sleep(999);
	close_screen();
	return 0;
}

C++

Simple Windows console implementation.

#include <windows.h>
#include <ctime>
#include <iostream>
#include <string>

const int WID = 60, HEI = 30, MAX_LEN = 600;
enum DIR { NORTH, EAST, SOUTH, WEST };

class snake {
public:
    snake() {
        console = GetStdHandle( STD_OUTPUT_HANDLE ); SetConsoleTitle( "Snake" ); 
        COORD coord = { WID + 1, HEI + 2 }; SetConsoleScreenBufferSize( console, coord );
        SMALL_RECT rc = { 0, 0, WID, HEI + 1 }; SetConsoleWindowInfo( console, TRUE, &rc );
        CONSOLE_CURSOR_INFO ci = { 1, false }; SetConsoleCursorInfo( console, &ci );
    }
    void play() {
        std::string a;
        while( 1 ) {
            createField(); alive = true;
            while( alive ) { drawField(); readKey(); moveSnake(); Sleep( 50 ); }
            COORD c = { 0, HEI + 1 }; SetConsoleCursorPosition( console, c );
            SetConsoleTextAttribute( console, 0x000b );
            std::cout << "Play again [Y/N]? "; std::cin >> a;
            if( a.at( 0 ) != 'Y' && a.at( 0 ) != 'y' ) return;
        }
    }
private:
    void createField() {
        COORD coord = { 0, 0 }; DWORD c;
        FillConsoleOutputCharacter( console, ' ', ( HEI + 2 ) * 80, coord, &c );
        FillConsoleOutputAttribute( console, 0x0000, ( HEI + 2 ) * 80, coord, &c );
        SetConsoleCursorPosition( console, coord );
        int x = 0, y = 1; for( ; x < WID * HEI; x++ ) brd[x] = 0;
        for( x = 0; x < WID; x++ ) {
            brd[x] = brd[x + WID * ( HEI - 1 )] = '+';
        }
        for( ; y < HEI; y++ ) {
            brd[0 + WID * y] = brd[WID - 1 + WID * y] = '+';
        }
        do {
            x = rand() % WID; y = rand() % ( HEI >> 1 ) + ( HEI >> 1 );
        } while( brd[x + WID * y] );
        brd[x + WID * y] = '@';
        tailIdx = 0; headIdx = 4; x = 3; y = 2;
        for( int c = tailIdx; c < headIdx; c++ ) {
            brd[x + WID * y] = '#';
            snk[c].X = 3 + c; snk[c].Y = 2;
        }
        head = snk[3]; dir = EAST; points = 0;
    }
    void readKey() {
        if( GetAsyncKeyState( 39 ) & 0x8000 ) dir = EAST;
        if( GetAsyncKeyState( 37 ) & 0x8000 ) dir = WEST;
        if( GetAsyncKeyState( 38 ) & 0x8000 ) dir = NORTH;
        if( GetAsyncKeyState( 40 ) & 0x8000 ) dir = SOUTH;
    }
    void drawField() {
        COORD coord; char t;
        for( int y = 0; y < HEI; y++ ) {
            coord.Y = y;
            for( int x = 0; x < WID; x++ ) {
                t = brd[x + WID * y]; if( !t ) continue;
                coord.X = x; SetConsoleCursorPosition( console, coord );
                if( coord.X == head.X && coord.Y == head.Y ) {
                    SetConsoleTextAttribute( console, 0x002e );
                    std::cout << 'O'; SetConsoleTextAttribute( console, 0x0000 );
                    continue;
                }
                switch( t ) {
                    case '#': SetConsoleTextAttribute( console, 0x002a ); break;
                    case '+': SetConsoleTextAttribute( console, 0x0019 ); break;
                    case '@': SetConsoleTextAttribute( console, 0x004c ); break;
                }
                std::cout << t; SetConsoleTextAttribute( console, 0x0000 );
            }
        }
        std::cout << t; SetConsoleTextAttribute( console, 0x0007 );
        COORD c = { 0, HEI }; SetConsoleCursorPosition( console, c );
        std::cout << "Points: " << points;
    }
    void moveSnake() {
        switch( dir ) {
            case NORTH: head.Y--; break;
            case EAST: head.X++; break;
            case SOUTH: head.Y++; break;
            case WEST: head.X--; break;
        }
        char t = brd[head.X + WID * head.Y];
        if( t && t != '@' ) { alive = false; return; }
        brd[head.X + WID * head.Y] = '#';
        snk[headIdx].X = head.X; snk[headIdx].Y = head.Y;
        if( ++headIdx >= MAX_LEN ) headIdx = 0;
        if( t == '@' ) {
            points++; int x, y;
            do {
                x = rand() % WID; y = rand() % ( HEI >> 1 ) + ( HEI >> 1 );
            } while( brd[x + WID * y] );
            brd[x + WID * y] = '@'; return;
        }
        SetConsoleCursorPosition( console, snk[tailIdx] ); std::cout << ' ';
        brd[snk[tailIdx].X + WID * snk[tailIdx].Y] = 0;
        if( ++tailIdx >= MAX_LEN ) tailIdx = 0;
    }
    bool alive; char brd[WID * HEI]; 
    HANDLE console; DIR dir; COORD snk[MAX_LEN];
    COORD head; int tailIdx, headIdx, points;
};
int main( int argc, char* argv[] ) {
    srand( static_cast<unsigned>( time( NULL ) ) );
    snake s; s.play(); return 0;
}

Delphi

Library: Vcl.Forms
Library: Vcl.Dialogs
Translation of: JavaScript
unit SnakeGame;

interface

uses
  Winapi.Windows, System.SysUtils,
  System.Classes, Vcl.Graphics, Vcl.Forms, Vcl.Dialogs,
  System.Generics.Collections, Vcl.ExtCtrls;

type
  TSnakeApp = class(TForm)
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure FormPaint(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure DoFrameStep(Sender: TObject);
    procedure Reset;
  private
    { Private declarations }
    FrameTimer: TTimer;
  public
    { Public declarations }
  end;

  TSnake = class
    len: Integer;
    alive: Boolean;
    pos: TPoint;
    posArray: TList<TPoint>;
    dir: Byte;
  private
    function Eat(Fruit: TPoint): Boolean;
    function Overlap: Boolean;
    procedure update;
  public
    procedure Paint(Canvas: TCanvas);
    procedure Reset;
    constructor Create;
    destructor Destroy; override;
  end;

  TFruit = class
    FruitTime: Boolean;
    pos: TPoint;
    constructor Create;
    procedure Reset;
    procedure Paint(Canvas: TCanvas);
  private
    procedure SetFruit;
  end;

const
  L = 1;
  R = 2;
  D = 4;
  U = 8;

var
  SnakeApp: TSnakeApp;
  block: Integer = 24;
  wid: Integer = 30;
  hei: Integer = 20;
  fruit: TFruit;
  snake: TSnake;

implementation

{$R *.dfm}

function Rect(x, y, w, h: Integer): TRect;
begin
  Result := TRect.Create(x, y, x + w, y + h);
end;

{ TSnake }

constructor TSnake.Create;
begin
  posArray := TList<TPoint>.Create;
  Reset;
end;

procedure TSnake.Paint(Canvas: TCanvas);
var
  pos: TPoint;
  i, l: Integer;
  r: TRect;
begin
  with Canvas do
  begin
    Brush.Color := rgb(130, 190, 0);

    i := posArray.count - 1;
    l := posArray.count;
    while True do
    begin
      pos := posArray[i];
      dec(i);
      r := rect(pos.x * block, pos.y * block, block, block);
      FillRect(r);
      dec(l);
      if l = 0 then
        Break;
    end;
  end;
end;

procedure TSnake.Reset;
begin
  alive := true;
  pos := Tpoint.Create(1, 1);
  posArray.Clear;
  posArray.Add(Tpoint.Create(pos));
  len := posArray.Count;
  dir := r;
end;

destructor TSnake.Destroy;
begin
  posArray.Free;
  inherited;
end;

function TSnake.Eat(Fruit: TPoint): Boolean;
begin
  result := (pos.X = Fruit.X) and (pos.y = Fruit.y);
  if result then
  begin
    inc(len);
    if len > 5000 then
      len := 500;
  end;
end;

function TSnake.Overlap: Boolean;
var
  aLen: Integer;
  tp: TPoint;
  i: Integer;
begin
  aLen := posArray.count - 1;

  for i := 0 to aLen - 1 do
  begin
    tp := posArray[i];
    if (tp.x = pos.x) and (tp.y = pos.y) then
      Exit(True);
  end;
  Result := false;
end;

procedure TSnake.update;
begin
  if not alive then
    exit;
  case dir of
    l:
      begin
        dec(pos.X);
        if pos.X < 1 then
          pos.x := wid - 2
      end;
    r:
      begin
        inc(pos.x);
        if (pos.x > (wid - 2)) then
          pos.x := 1;
      end;
    U:
      begin
        dec(pos.y);

        if (pos.y < 1) then
          pos.y := hei - 2
      end;
    D:
      begin
        inc(pos.y);

        if (pos.y > hei - 2) then
          pos.y := 1;
      end;
  end;
  if Overlap then
    alive := False
  else
  begin
    posArray.Add(TPoint(pos));

    if len < posArray.Count then
      posArray.Delete(0);
  end;
end;

{ TFruit }

constructor TFruit.Create;
begin
  Reset;
end;

procedure TFruit.Paint(Canvas: TCanvas);
var
  r: TRect;
begin
  with Canvas do
  begin
    Brush.Color := rgb(200, 50, 20);

    r := Rect(pos.x * block, pos.y * block, block, block);

    FillRect(r);
  end;
end;

procedure TFruit.Reset;
begin
  fruitTime := true;
  pos := Tpoint.Create(0, 0);
end;

procedure TFruit.SetFruit;
begin
  pos.x := Trunc(Random(wid - 2) + 1);
  pos.y := Trunc(Random(hei - 2) + 1);
  fruitTime := false;
end;

procedure TSnakeApp.DoFrameStep(Sender: TObject);
begin
  Invalidate;
end;

procedure TSnakeApp.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  FrameTimer.Free;
  snake.Free;
  Fruit.Free;
end;

procedure TSnakeApp.FormCreate(Sender: TObject);
begin
  Canvas.pen.Style := psClear;
  ClientHeight := block * hei;
  ClientWidth := block * wid;
  DoubleBuffered := True;
  KeyPreview := True;

  OnClose := FormClose;
  OnKeyDown := FormKeyDown;
  OnPaint := FormPaint;

  snake := TSnake.Create;
  Fruit := TFruit.Create();
  FrameTimer := TTimer.Create(nil);
  FrameTimer.Interval := 250;
  FrameTimer.OnTimer := DoFrameStep;
  FrameTimer.Enabled := True;
end;

procedure TSnakeApp.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);

  function ValidDir(value: Byte): Byte;
  var
    combination: Byte;
  begin
    combination := (value or snake.dir);
    if (combination = 3) or (combination = 12) then
      Result := snake.dir
    else
      Result := value;
  end;

begin
  case Key of
    VK_LEFT:
      snake.dir := ValidDir(l);
    VK_RIGHT:
      snake.dir := ValidDir(r);
    VK_UP:
      snake.dir := ValidDir(U);
    VK_DOWN:
      snake.dir := ValidDir(D);
    VK_ESCAPE:
      Reset;
  end;
end;

procedure TSnakeApp.FormPaint(Sender: TObject);
var
  i: Integer;
  r: TRect;
  frameR: Double;
begin
  with Canvas do
  begin
    Brush.Color := rgb(0, $22, 0);
    FillRect(ClipRect);
    Brush.Color := rgb(20, 50, 120);

    for i := 0 to wid - 1 do
    begin
      r := rect(i * block, 0, block, block);
      FillRect(r);
      r := rect(i * block, ClientHeight - block, block, block);
      FillRect(r);
    end;

    for i := 1 to hei - 2 do
    begin
      r := Rect(1, i * block, block, block);
      FillRect(r);
      r := Rect(ClientWidth - block, i * block, block, block);
      FillRect(r);
    end;

    if (Fruit.fruitTime) then
    begin
      Fruit.setFruit();
      frameR := FrameTimer.Interval * 0.95;
      if frameR < 30 then
        frameR := 30;
      FrameTimer.Interval := trunc(frameR);
    end;

    Fruit.Paint(Canvas);
    snake.update();

    if not snake.alive then
    begin
      FrameTimer.Enabled := False;
      Application.ProcessMessages;
      ShowMessage('Game over');
      Reset;
      exit;
    end;

    if (snake.eat(Fruit.pos)) then
      Fruit.fruitTime := true;
    snake.Paint(Canvas);

    Brush.Style := bsClear;
    Font.Color := rgb(200, 200, 200);
    Font.Size := 18;
    TextOut(50, 0, (snake.len - 1).ToString);
  end;
end;

procedure TSnakeApp.Reset;
begin
  snake.Reset;
  Fruit.Reset;
  FrameTimer.Interval := 250;
  FrameTimer.Enabled := True;
end;
end.

Form resources:

object SnakeApp: TSnakeApp
  OnCreate = FormCreate
end

EasyLang

Run it

Translation of: Craft Basic
subr fruit
   rx = (randint 20 - 1) * 5 + 2.5
   ry = (randint 20 - 1) * 5 + 2.5
.
subr start
   fruit
   game = 1
   sx[] = [ 52.5 0 0 0 0 ]
   sy[] = [ 52.5 0 0 0 0 ]
   dir = randint 4
   timer 0
.
background 242
move 30 70
clear
color 997
text "SNAKE"
textsize 5
move 6 40
text "Keys or mouse for controlling"
move 6 30
text "Space or click to to start"
#
on key
   if game = 0 and keybkey = " "
      start
      return
   .
   if dir mod 2 = 1
      if keybkey = "ArrowRight"
         dir = 2
      elif keybkey = "ArrowLeft"
         dir = 4
      .
   else
      if keybkey = "ArrowUp"
         dir = 1
      elif keybkey = "ArrowDown"
         dir = 3
      .
   .
.
on mouse_down
   if game = 0
      start
      return
   .
   if dir mod 2 = 1
      if mouse_x < sx
         dir = 4
      else
         dir = 2
      .
   else
      if mouse_y < sy
         dir = 3
      else
         dir = 1
      .
   .
.
on timer
   clear
   color 997
   move 2 95
   text "Score: " & 10 * len sx[] - 50
   color 966
   move rx ry
   circle 1.5
   #
   sx = sx[1] ; sy = sy[1]
   if dir = 1
      sy += 5
   elif dir = 2
      sx += 5
   elif dir = 3
      sy -= 5
   elif dir = 4
      sx -= 5
   .
   if sx < 0 or sx > 100 or sy < 0 or sy > 100
      game = 0
   .
   color 494
   for i = len sx[] downto 2
      if sx = sx[i] and sy = sy[i]
         game = 0
      .
      sx[i] = sx[i - 1]
      sy[i] = sy[i - 1]
      if sx[i] > 0
         move sx[i] sy[i]
         circle 2.5
      .
   .
   move sx sy
   circle 2.5
   color 000
   if dir = 2 or dir = 4
      move sx sy + 1
      circle 0.5
      move sx sy - 1
      circle 0.5
   else
      move sx + 1 sy
      circle 0.5
      move sx - 1 sy
      circle 0.5
   .
   if sx = rx and sy = ry
      len sx[] len sx[] + 3
      len sy[] len sy[] + 3
      fruit
   .
   sx[1] = sx ; sy[1] = sy
   if game = 1
      timer 0.15
   else
      color 997
      move 10 10
      text "Space or click new game"
   .
.

F#

open System
open System.Threading.Tasks
open System.Threading

module SnakeGame = 

    /// 🚏 Directions on our grid:
    type Movement =
        | Left of Position
        | Right of Position
        | Down of Position
        | Up of Position
        | InvalidMove

    /// 🐍 Sort of like a list, but not:
    and Snake<'Position> =
        | Head of Position * Snake<'Position>
        | Belly of Position * Snake<'Position>
        | Tail

    /// 🕹️ Our basic runtime information:
    and Game = 
        | GameRunning of Movement * Snake<Position> * Grid * Eaten:int * Food
        | GameOver of Grid * Eaten:int

    /// 🧭 x & y in our plane:
    and Position = int * int

    /// 🍎 The food our snake will eat:
    and Food = Position * string
    
    /// 🌐 A simple two dimensional plane:
    and Grid = string[][]

    /// Making a list of positions from a Snake 🐍
    let snakeUnzip (snake:Snake<Position>) =
        let rec unzip snake carry =
            match snake with
            | Head (p, rest) -> unzip rest <| carry @ [p]
            | Belly (p, rest) -> unzip rest <| carry @ [p]
            | Tail -> carry
        unzip snake []
        
    /// Making a Snake from a list of positions 🐍
    let snakeZip (positions:list<Position>) (upto:int) =
        let correctLength = (List.take upto positions)
        let rec zip (carry:Snake<Position>) (rest:list<Position>) =
            match rest with
            | head::[] -> zip (Head(head, carry)) []
            | back::front -> zip (Belly (back, carry)) front
            | [] -> carry
        zip Tail (List.rev correctLength)

    module Graphics = 
        let private random = new Random()
        let private head = "🤢"
        let private belly = "🟢"
        let private display = "⬜"
        let private errorDisplay = "🟥"

        let private food = [|"🐁";"🐀";"🐥";"🪺";"🐸";"🐛";"🪰";"🐞";"🦗"|]
        let private randomFood () = food.[random.Next(food.Length - 1)]

        let isFood square = Array.contains square food
        let isFreeSpace square = square = display || square = errorDisplay
        let isOccupied square = 
            match square with 
            | square when isFreeSpace square -> false 
            | square when isFood square -> false
            | _ -> true

        let makeGrid (dimensionsSquared) : Grid = 
            let row _ = Array.init dimensionsSquared (fun _ -> display)
            Array.init dimensionsSquared row

        let clearGrid (grid:Grid) : unit =
            Array.iteri (fun i row -> 
                Array.iteri (fun j _ ->
                    grid.[i].[j] <- display
                ) row
            ) grid

        let render (grid:Grid) : unit =
            Console.Clear()
            Array.iter (fun (row:string array) -> 
                let prettyprint = String.concat "" row
                printfn $"{prettyprint}") grid
            printfn "Snake Game in FSharp by @wiredsister"
            printfn "Controls: ⬅️ ↕️ ➡️"
            printfn "Press Ctrl+C to Quit Game"
            Console.Title <- "FSharp Snake 🐍"

        let getFreeSpaces (grid:Grid) : list<Position> =
            let results : Position list ref = ref []
            for i in 0..(grid.Length-1) do
                for j in 0..(grid.Length-1) do
                    if isFreeSpace grid.[i].[j]
                    then results.Value <- results.Value @ [i,j]
                    else ()
                ()
            results.Value

        let getFood (grid:Grid) : Food =
            Console.Beep()
            let freeSpaces = 
                getFreeSpaces grid
                |> Array.ofList
            let food = randomFood ()
            let randomPos = freeSpaces.[random.Next(freeSpaces.Length - 1)]
            randomPos, food

        let dropFood (grid:Grid) (food:Food) =
            let (x, y), animal = food
            grid.[x].[y] <- animal

        let slither (snake:Snake<Position>) (grid:Grid) : unit =
            try
                let rec slithering (body:Snake<Position>) =
                    match body with
                    | Head(p, s) -> 
                        let row, column = p
                        grid.[row].[column] <- head 
                        slithering s
                    | Belly(p, s) -> 
                        let row, column = p
                        grid.[row].[column] <- belly 
                        slithering s
                    | Tail -> ()
                do slithering snake
            with _ -> failwith "ERROR: Could not slither snake!"
        
        let endGame (grid:Grid) : unit =
            Console.Clear()
            Array.iteri (fun i row -> 
                Array.iteri (fun j _ ->
                    grid.[i].[j] <- errorDisplay
                ) row
            ) grid
            Array.iter (fun (row:string array) -> 
                let prettyprint = String.concat "" row
                printfn $"{prettyprint}") grid
            Console.Beep()

    
    module GamePlay =

        let moveUp (snake:Snake<Position>) (grid:Grid) (eaten:int) (food:Food) : Game =
            match snake with 
            | Head (p, rest:Snake<Position>) ->
                let x, y = p
                let shiftUp = ((x-1), y)
                try
                    match shiftUp with
                    | (row,column) when Graphics.isOccupied grid.[row].[column] ->
                        GameOver (grid, eaten)
                    | (row, column) when Graphics.isFood grid.[row].[column] ->
                        let unzipped = snakeUnzip (Head (shiftUp, (Belly (p, rest))))
                        let newSnake = snakeZip unzipped (eaten+1)
                        let nextFood = Graphics.getFood grid
                        GameRunning (Up shiftUp, newSnake, grid, eaten+1, nextFood)
                    | pivot ->
                        let unzipped = snakeUnzip (Head (pivot, (Belly (p, rest))))
                        let newSnake = snakeZip unzipped eaten
                        GameRunning (Up pivot, newSnake, grid, eaten, food)
                with _ -> GameOver (grid, eaten)
            | _ -> failwith "ERROR: No head!"

        let moveDown (snake:Snake<Position>) (grid:Grid) (eaten:int) (food:Food) : Game =
            match snake with 
            | Head (p, rest:Snake<Position>) ->
                let x, y = p
                let shiftDown = ((x+1), y)
                try
                    match shiftDown with
                    | (row,column) when Graphics.isOccupied grid.[row].[column] ->
                        GameOver (grid, eaten)
                    | (row, column) when Graphics.isFood grid.[row].[column] ->
                        let unzipped = snakeUnzip (Head (shiftDown, (Belly (p, rest))))
                        let newSnake = snakeZip unzipped (eaten+1)
                        let nextFood = Graphics.getFood grid
                        GameRunning (Down shiftDown, newSnake, grid, (eaten+1), nextFood)
                    | pivot ->
                        let unzipped = snakeUnzip (Head (pivot, (Belly (p, rest))))
                        let newSnake = snakeZip unzipped eaten
                        GameRunning (Down pivot, newSnake, grid, eaten, food)
                with _ -> GameOver (grid, eaten)
            | _ -> failwith "ERROR: No head!"

        let moveLeft (snake:Snake<Position>) (grid:Grid) (eaten:int) (food:Food) : Game =
            match snake with 
            | Head (p, rest:Snake<Position>) ->
                let x, y = p
                let shiftLeft = (x, (y-1))
                try
                    match shiftLeft with
                    | (row,column) when Graphics.isOccupied grid.[row].[column] ->
                        GameOver (grid, eaten)
                    | (row, column) when Graphics.isFood grid.[row].[column] ->
                        let unzipped = snakeUnzip (Head (shiftLeft, (Belly (p, rest))))
                        let newSnake = snakeZip unzipped (eaten+1)
                        let nextFood = Graphics.getFood grid
                        GameRunning (Left shiftLeft, newSnake, grid, eaten+1, nextFood)
                    | pivot ->
                        let unzipped = snakeUnzip (Head (pivot, (Belly (p, rest))))
                        let newSnake = snakeZip unzipped eaten
                        GameRunning (Left pivot, newSnake, grid, eaten, food)
                with _ -> GameOver (grid, eaten)
            | _ -> failwith "ERROR: No head!"

        let moveRight (snake:Snake<Position>) (grid:Grid) (eaten:int) (food:Food) : Game =
            match snake with 
            | Head (p, rest:Snake<Position>) ->
                let (x: int), y = p
                let shiftRight = (x, (y+1))
                try
                    match shiftRight with
                    | (row,column) when Graphics.isOccupied grid.[row].[column] ->
                        GameOver (grid, eaten)
                    | (row, column) when Graphics.isFood grid.[row].[column] ->
                        let unzipped = snakeUnzip (Head (shiftRight, (Belly (p, rest))))
                        let newSnake = snakeZip unzipped (eaten+1)
                        let nextFood = Graphics.getFood grid
                        GameRunning (Right shiftRight, newSnake, grid, eaten+1, nextFood)
                    | pivot ->
                        let unzipped = snakeUnzip (Head (pivot, (Belly (p, rest))))
                        let newSnake = snakeZip unzipped eaten
                        GameRunning (Right pivot, newSnake, grid, eaten, food)
                with _ -> GameOver (grid, eaten)
            | _ -> failwith "ERROR: No head!"

open SnakeGame

[<EntryPoint>]
let main _ =

    /// A gentle slope function for making the snake go faster:
    let tick (eaten:int) = 100./log10(float eaten) |> int

    let getNextMove prev snake grid eaten food : Task<Game> = 
        task {
            do! Task.Delay(tick(eaten))
            if not Console.KeyAvailable
            then 
                match prev with
                    | Up _ -> return GamePlay.moveUp snake grid eaten food
                    | Down _ -> return GamePlay.moveDown snake grid eaten food
                    | Right _ -> return GamePlay.moveRight snake grid eaten food
                    | Left _ -> return GamePlay.moveLeft snake grid eaten food
                    | InvalidMove -> return GameOver (grid, eaten)
            else
                match Console.ReadKey() with
                | keystroke when keystroke.Key.Equals(ConsoleKey.UpArrow) ->
                    return GamePlay.moveUp snake grid eaten food
                | keystroke when keystroke.Key.Equals(ConsoleKey.DownArrow) ->
                    return GamePlay.moveDown snake grid eaten food
                | keystroke when keystroke.Key.Equals(ConsoleKey.RightArrow) ->
                    return GamePlay.moveRight snake grid eaten food
                | keystroke when keystroke.Key.Equals(ConsoleKey.LeftArrow) ->
                    return GamePlay.moveLeft snake grid eaten food
                | _ ->
                    match prev with
                    | Up _ -> return GamePlay.moveUp snake grid eaten food
                    | Down _ -> return GamePlay.moveDown snake grid eaten food
                    | Right _ -> return GamePlay.moveRight snake grid eaten food
                    | Left _ -> return GamePlay.moveLeft snake grid eaten food
                    | InvalidMove -> return GameOver (grid, eaten)
        }

    let gridDimension = 20
    let segments = [(0,3); (0,2); (0,1); (0,0)]
    let youngSnake : Snake<Position> = snakeZip segments segments.Length
    let startingGrid = Graphics.makeGrid gridDimension
    let startingFood = Graphics.getFood startingGrid
    let start = GamePlay.moveRight youngSnake startingGrid segments.Length startingFood
    
    let rec gameLoop (game:Game) =
        match game with
        | GameRunning (prev, snake, grid, eaten, food) ->
            do Graphics.clearGrid grid
            do Graphics.dropFood grid food
            do Graphics.slither snake grid
            do Graphics.render grid
            let eitherPlayerOrCursor = getNextMove prev snake grid eaten food
            do eitherPlayerOrCursor.Wait()
            gameLoop eitherPlayerOrCursor.Result
        | GameOver (grid,eaten) ->
            do Graphics.endGame grid
            printfn $"Game Over! Snake ate {eaten-segments.Length} critters!"
            do Thread.Sleep(1000)
            let rec wantToPlayAgain () =
                match Console.ReadKey() with
                | keystroke when keystroke.Key.Equals(ConsoleKey.Y)  -> gameLoop start
                | keystroke when keystroke.Key.Equals(ConsoleKey.N)  -> ()
                | _ -> wantToPlayAgain ()
            printfn $"Restart? Type Y to continue..."
            wantToPlayAgain()

    do gameLoop start
    0

Go

This uses the termbox package for terminal input and output. This makes the code fairly cross-platform, it successfully built for FreeBSD, OpenBSD, NetBSD, DragonFly BSD, Linux, MS Windows, and MacOS (tested on FreeBSD and MS Windows).

package main

import (
	"errors"
	"fmt"
	"log"
	"math/rand"
	"time"

	termbox "github.com/nsf/termbox-go"
)

func main() {
	rand.Seed(time.Now().UnixNano())
	score, err := playSnake()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Final score:", score)
}

type snake struct {
	body          []position // tail to head positions of the snake
	heading       direction
	width, height int
	cells         []termbox.Cell
}

type position struct {
	X int
	Y int
}

type direction int

const (
	North direction = iota
	East
	South
	West
)

func (p position) next(d direction) position {
	switch d {
	case North:
		p.Y--
	case East:
		p.X++
	case South:
		p.Y++
	case West:
		p.X--
	}
	return p
}

func playSnake() (int, error) {
	err := termbox.Init()
	if err != nil {
		return 0, err
	}
	defer termbox.Close()

	termbox.Clear(fg, bg)
	termbox.HideCursor()
	s := &snake{
		// It would be more efficient to use a circular
		// buffer instead of a plain slice for s.body.
		body:  make([]position, 0, 32),
		cells: termbox.CellBuffer(),
	}
	s.width, s.height = termbox.Size()
	s.drawBorder()
	s.startSnake()
	s.placeFood()
	s.flush()

	moveCh, errCh := s.startEventLoop()
	const delay = 125 * time.Millisecond
	for t := time.NewTimer(delay); ; t.Reset(delay) {
		var move direction
		select {
		case err = <-errCh:
			return len(s.body), err
		case move = <-moveCh:
			if !t.Stop() {
				<-t.C // handles race between moveCh and t.C
			}
		case <-t.C:
			move = s.heading
		}
		if s.doMove(move) {
			time.Sleep(1 * time.Second)
			break
		}
	}

	return len(s.body), err
}

func (s *snake) startEventLoop() (<-chan direction, <-chan error) {
	moveCh := make(chan direction)
	errCh := make(chan error, 1)
	go func() {
		defer close(errCh)
		for {
			switch ev := termbox.PollEvent(); ev.Type {
			case termbox.EventKey:
				switch ev.Ch { // WSAD and HJKL movement
				case 'w', 'W', 'k', 'K':
					moveCh <- North
				case 'a', 'A', 'h', 'H':
					moveCh <- West
				case 's', 'S', 'j', 'J':
					moveCh <- South
				case 'd', 'D', 'l', 'L':
					moveCh <- East
				case 0:
					switch ev.Key { // Cursor key movement
					case termbox.KeyArrowUp:
						moveCh <- North
					case termbox.KeyArrowDown:
						moveCh <- South
					case termbox.KeyArrowLeft:
						moveCh <- West
					case termbox.KeyArrowRight:
						moveCh <- East
					case termbox.KeyEsc: // Quit
						return
					}
				}
			case termbox.EventResize:
				// TODO
				errCh <- errors.New("terminal resizing unsupported")
				return
			case termbox.EventError:
				errCh <- ev.Err
				return
			case termbox.EventInterrupt:
				return
			}
		}
	}()
	return moveCh, errCh
}

func (s *snake) flush() {
	termbox.Flush()
	s.cells = termbox.CellBuffer()
}

func (s *snake) getCellRune(p position) rune {
	i := p.Y*s.width + p.X
	return s.cells[i].Ch
}
func (s *snake) setCell(p position, c termbox.Cell) {
	i := p.Y*s.width + p.X
	s.cells[i] = c
}

func (s *snake) drawBorder() {
	for x := 0; x < s.width; x++ {
		s.setCell(position{x, 0}, border)
		s.setCell(position{x, s.height - 1}, border)
	}
	for y := 0; y < s.height-1; y++ {
		s.setCell(position{0, y}, border)
		s.setCell(position{s.width - 1, y}, border)
	}
}

func (s *snake) placeFood() {
	for {
		// a random empty cell
		x := rand.Intn(s.width-2) + 1
		y := rand.Intn(s.height-2) + 1
		foodp := position{x, y}
		r := s.getCellRune(foodp)
		if r != ' ' {
			continue
		}
		s.setCell(foodp, food)
		return
	}
}

func (s *snake) startSnake() {
	// a random cell somewhat near the center
	x := rand.Intn(s.width/2) + s.width/4
	y := rand.Intn(s.height/2) + s.height/4
	head := position{x, y}
	s.setCell(head, snakeHead)
	s.body = append(s.body[:0], head)
	s.heading = direction(rand.Intn(4))
}

func (s *snake) doMove(move direction) bool {
	head := s.body[len(s.body)-1]
	s.setCell(head, snakeBody)
	head = head.next(move)
	s.heading = move
	s.body = append(s.body, head)
	r := s.getCellRune(head)
	s.setCell(head, snakeHead)
	gameOver := false
	switch r {
	case food.Ch:
		s.placeFood()
	case border.Ch, snakeBody.Ch:
		gameOver = true
		fallthrough
	case empty.Ch:
		s.setCell(s.body[0], empty)
		s.body = s.body[1:]
	default:
		panic(r)
	}
	s.flush()
	return gameOver
}

const (
	fg = termbox.ColorWhite
	bg = termbox.ColorBlack
)

// Symbols to use.
// Could use Unicode instead of simple ASCII.
var (
	empty     = termbox.Cell{Ch: ' ', Bg: bg, Fg: fg}
	border    = termbox.Cell{Ch: '+', Bg: bg, Fg: termbox.ColorBlue}
	snakeBody = termbox.Cell{Ch: '#', Bg: bg, Fg: termbox.ColorGreen}
	snakeHead = termbox.Cell{Ch: 'O', Bg: bg, Fg: termbox.ColorYellow | termbox.AttrBold}
	food      = termbox.Cell{Ch: '@', Bg: bg, Fg: termbox.ColorRed}
)

Haskell

{-# LANGUAGE TemplateHaskell #-}
import Control.Monad.Random (getRandomRs)
import Graphics.Gloss.Interface.Pure.Game
import Lens.Micro ((%~), (^.), (&), set)
import Lens.Micro.TH (makeLenses)

--------------------------------------------------------------------------------
-- all data types

data Snake = Snake { _body :: [Point], _direction :: Point }
makeLenses ''Snake

data World = World { _snake :: Snake , _food :: [Point]
                   , _score :: Int , _maxScore :: Int }
makeLenses ''World

--------------------------------------------------------------------------------
-- everything snake can do

moves (Snake b d) = Snake (step b d : init b) d
eats  (Snake b d) = Snake (step b d : b) d
bites (Snake b _) = any (== head b)
step ((x,y):_) (a,b) = (x+a, y+b)

turn (x',y') (Snake b (x,y)) | (x+x',y+y') == (0,0)  = Snake b (x,y)
                             | otherwise             = Snake b (x',y')

--------------------------------------------------------------------------------
-- all randomness

createWorld = do xs <- map fromIntegral <$> getRandomRs (2, 38 :: Int)
                 ys <- map fromIntegral <$> getRandomRs (2, 38 :: Int)
                 return (Ok, World snake (zip xs ys) 0 0)
                 where
                   snake = Snake [(20, 20)] (1,0)
                 
-------------------------------------------------------------------------------
-- A tyny DSL for declarative description of business logic 

data Status = Ok | Fail | Stop

continue = \x -> (Ok, x)
stop     = \x -> (Stop, x)
f >>> g  = \x -> case f x of { (Ok, y) -> g y; b -> b }    -- chain composition
f <|> g  = \x -> case f x of { (Fail, _) -> g x; b -> b }  -- alternative
p ==> f  = \x -> if p x then f x else (Fail, x)            -- condition
l .& f   = continue . (l %~ f)                             -- modification
l .= y   = continue . set l y                              -- setting

--------------------------------------------------------------------------------
-- all business logic

updateWorld _ =  id >>> (snakeEats <|> snakeMoves) 
  where
    snakeEats  = (snakeFindsFood ==> (snake .& eats)) >>>
                 (score .& (+1)) >>> (food .& tail)

    snakeMoves = (snakeBitesTail ==> stop) <|>
                 (snakeHitsWall ==> stop) <|>
                 (snake .& moves)

    snakeFindsFood w = (w^.snake & moves) `bites` (w^.food & take 1)
    snakeBitesTail w = (w^.snake) `bites` (w^.snake.body & tail)
    snakeHitsWall w  = (w^.snake.body) & head & isOutside
    isOutside (x,y) = or [x <= 0, 40 <= x, y <= 0, 40 <= y]

--------------------------------------------------------------------------------
-- all event handing

handleEvents e (s,w) = f w
  where f = case s of
          Ok -> case e of
            EventKey (SpecialKey k) _ _ _ -> case k of
              KeyRight -> snake .& turn (1,0)
              KeyLeft  -> snake .& turn (-1,0)
              KeyUp    -> snake .& turn (0,1)
              KeyDown  -> snake .& turn (0,-1)
              _-> continue
            _-> continue
          _-> \w -> w & ((snake.body) .= [(20,20)]) >>>
                         (maxScore .& max (w^.score)) >>> (score .= 0)
                         
--------------------------------------------------------------------------------
-- all graphics

renderWorld (s, w) = pictures [frame, color c drawSnake, drawFood, showScore]
  where c = case s of { Ok -> orange; _-> red }
        drawSnake = foldMap (rectangleSolid 10 10 `at`) (w^.snake.body)
        drawFood = color blue $ circleSolid 5 `at` (w^.food & head)
        frame = color black $ rectangleWire 400 400
        showScore = color orange $ scale 0.2 0.2 $ txt `at` (-80,130)
        txt = Text $ mconcat ["Score: ", w^.score & show
                             ,"   Maximal score: ", w^.maxScore & show]
        at p (x,y) = Translate (10*x-200) (10*y-200) p

--------------------------------------------------------------------------------

main = do world <- createWorld
          play inW white 7 world renderWorld handleEvents updateWorld
  where inW = InWindow "The Snake" (400, 400) (10, 10)

Extra credit

It is easy to make snake to seek food automatically. Just change the first line of the updateWorld definition:

updateWorld _ =  id >>> snakeSeeksFood >>> (snakeEats <|> snakeMoves)

and add local definition:

    snakeSeeksFood w = w & snake .& turns optimalDirection
      where
        optimalDirection = minimumBy (comparing distanceToFood) safeTurns
        
        safeTurns = filter safe [(x,y),(-y,x),(y,-x)] `ifEmpty` [(x,y)]
          where (x,y) = w^.snake.direction
                safe d = let w'' = w & snake %~ moves . turns d
                         in not (snakeBitesTail w'' || snakeHitsWall w'')
                lst `ifEmpty` x = if null lst then x else lst
        
        distanceToFood d = let (a,b) = w^.snake & turns d & moves & (^.body) & head
                               (x,y) = w^.food & head
                           in (a-x)^2 + (b-y)^2

J

Needs j9 qt:

Use WASD to move. Use start to start.

Difficulty may be adjusted by providing a non-default argument to start (time in milliseconds between animation updates). For example, start 200 for an easier game.

In this implementation, speed is fixed (and final score is by the update rate). Another approach might be to start slow and gradually speed based on the length of the snake. Those sorts of changes are left as an exercise for the reader.

require'ide/qt/gl2'
coinsert 'jgl2'

open=: wd@{{)n
  pc s closeok;
  cc n isidraw;
  set n wh 400 400;
  pshow;
}}

start=: {{
  speed=: {.y,200
  open''
  snake=: 10 10,10 11,:10 12
  newdot''
  draw''
}}

newdot=: {{
  dot=: ({~ ?@#) snake -.~ ,/(#: i.)40 40
}}

s_n_char=: {{
  select. {.toupper sysdata
   case. 'W' do. move  0 _1
   case. 'A' do. move _1  0
   case. 'S' do. move  0  1
   case. 'D' do. move  1  0
  end.
}}

move=: {{
  s_timer=: move@y
  wd 'ptimer ',":speed
  head=. y+{.snake
  if. head e. snake do. head gameover'' return. end.
  if. _1 e. head do. (0>.head) gameover'' return. end.
  if. 1 e. 40 <: head do. (39<.head) gameover'' return. end.
  if. dot -: head do.
    snake=: dot,snake
    newdot''
  else.
    snake=: head,}: snake
  end.
  draw''
}}

draw=: {{
  glclear''
  glbrush glrgb 0 0 255
  glrect 10*}.snake,"1(1 1)
  glbrush glrgb 0 255 0
  glrect 10*({.snake),1 1
  glbrush glrgb 255 0 0
  glrect 10*dot,1 1
  glpaint''
  EMPTY
}}

gameover=: {{
  wd 'ptimer 0'
  if. 1<#snake do.
    echo 'game over'
    echo 'score: ',":(#snake)*1000%speed
    draw''
    glbrush glrgb 255 255 0
    glrect 10*x,1 1
  end.
  snake=: ,:_ _
}}

Java

See Snake/Java.

JavaScript

You need the P5 Library to run this code!

const L = 1, R = 2, D = 4, U = 8;
var block = 24, wid = 30, hei = 20, frameR = 7, fruit, snake;
function Snake() {
    this.length = 1;
    this.alive = true;
    this.pos = createVector( 1, 1 );
    this.posArray = [];
    this.posArray.push( createVector( 1, 1 ) );
    this.dir = R;
    this.draw = function() {
        fill( 130, 190, 0 );
        var pos, i = this.posArray.length - 1, l = this.length;
        while( true ){
            pos = this.posArray[i--];
            rect( pos.x * block, pos.y * block, block, block );
            if( --l == 0 ) break;
        }
    }
    this.eat = function( frut ) {
        var b = this.pos.x == frut.x && this.pos.y == frut.y;
        if( b ) this.length++;
        return b;
    }
    this.overlap = function() {
        var len = this.posArray.length - 1;
        for( var i = len; i > len - this.length; i-- ) {
            tp = this.posArray[i];
            if( tp.x === this.pos.x && tp.y === this.pos.y ) return true;
        }
        return false;
    }
    this.update = function() {
        if( !this.alive ) return;
        switch( this.dir ) {
            case L:
                this.pos.x--; if( this.pos.x < 1 ) this.pos.x = wid - 2;
            break;
            case R:
                this.pos.x++; if( this.pos.x > wid - 2 ) this.pos.x = 1;
            break;
            case U:
                this.pos.y--; if( this.pos.y < 1 ) this.pos.y = hei - 2;
            break;
            case D:
                this.pos.y++; if( this.pos.y > hei - 2 ) this.pos.y = 1;
            break;
        }
        if( this.overlap() ) { this.alive = false; } else {
            this.posArray.push( createVector( this.pos.x, this.pos.y ) );
            if( this.posArray.length > 5000 ) { this.posArray.splice( 0, 1 ); }
        }
    }
}
function Fruit() {
    this.fruitTime = true;
    this.pos = createVector();
    this.draw = function() {
        fill( 200, 50, 20 );
        rect( this.pos.x * block, this.pos.y * block, block, block );
    }

    this.setFruit = function() {
        this.pos.x = floor( random( 1, wid - 1 ) );
        this.pos.y = floor( random( 1, hei - 1 ) );
        this.fruitTime = false;
    }
}
function setup() {
    createCanvas( block * wid, block * hei );
    noStroke(); frameRate( frameR );
    snake = new Snake();fruit = new Fruit();
}
function keyPressed() {
    switch( keyCode ) {
        case LEFT_ARROW: snake.dir = L; break;
        case RIGHT_ARROW: snake.dir = R; break;
        case UP_ARROW: snake.dir = U; break;
        case DOWN_ARROW: snake.dir = D;
    }
}
function draw() {
    background( color( 0, 0x22, 0 ) );
    fill( 20, 50, 120 );
    for( var i = 0; i < wid; i++ ) {
        rect( i * block, 0, block, block );
        rect( i * block, height - block, block, block );
    }
    for( var i = 1; i < hei - 1; i++ ) {
        rect( 1, i * block, block, block );
        rect( width - block, i * block, block, block );
    }
    if( fruit.fruitTime ) {
        fruit.setFruit();
        frameR += .2;
        frameRate( frameR );
    }
    fruit.draw();
    snake.update();
    if( snake.eat( fruit.pos ) ) {
        fruit.fruitTime = true;
    }
    snake.draw();
    fill( 200 );
    textStyle( BOLD ); textAlign( RIGHT ); textSize( 120 );
    text( ""+( snake.length - 1 ), 690, 440 );
    if( !snake.alive ) text( "THE END", 630, 250 );
}

Julia

using GLMakie, GeometryBasics

mutable struct SnakeGame
    height::Int
    width::Int
    snake::Vector{CartesianIndex{2}}
    food::CartesianIndex{2}
end

function SnakeGame(;height=6, width=8)
    snake = [rand(CartesianIndices((height, width)))]
    food = rand(CartesianIndices((height, width)))
    while food == snake[1]
        food = rand(CartesianIndices((height, width)))
    end
    SnakeGame(height, width, snake, food)
end

function step!(game, direction)
    next_head = game.snake[1] + direction
    next_head = CartesianIndex(mod.(next_head.I, Base.OneTo.((game.height, game.width))))  # allow crossing boundry
    if is_valid(game, next_head)
        pushfirst!(game.snake, next_head)
        if next_head == game.food
            length(game.snake) < game.height * game.width && init_food!(game)
        else
            pop!(game.snake)
        end
        true
    else
        false
    end
end

is_valid(game, position) = position  game.snake

function init_food!(game)
    p = rand(CartesianIndices((game.height, game.width)))
    while !is_valid(game, p)
        p = rand(CartesianIndices((game.height, game.width)))
    end
    game.food = p
end

function play(;n=10,t=0.5)
    game = Observable(SnakeGame(;width=n,height=n))
    scene = Scene(resolution = (1000, 1000), raw = true, camera = campixel!)
    display(scene)

    area = scene.px_area
    poly!(scene, area)

    grid_size = @lift((widths($area)[1] / $game.height, widths($area)[2] / $game.width))

    snake_boxes = @lift([FRect2D((p.I .- (1,1)) .* $grid_size , $grid_size) for p in $game.snake])
    poly!(scene, snake_boxes, color=:blue, strokewidth = 5, strokecolor = :black)

    snake_head_box = @lift(FRect2D(($game.snake[1].I .- (1,1)) .* $grid_size , $grid_size))
    poly!(scene, snake_head_box, color=:black)
    snake_head = @lift((($game.snake[1].I .- 0.5) .* $grid_size))
    scatter!(scene, snake_head, marker='◉', color=:blue, markersize=@lift(minimum($grid_size)))

    food_position = @lift(($game.food.I .- (0.5,0.5)) .* $grid_size)
    scatter!(scene, food_position, color=:red, marker='♥', markersize=@lift(minimum($grid_size)))

    score_text = @lift("Score: $(length($game.snake)-1)")
    text!(scene, score_text, color=:gray, position = @lift((widths($area)[1]/2, widths($area)[2])), fontsize = 50, align = (:center, :top))

    direction = Ref{Any}(nothing)

    on(events(scene).keyboardbutton) do but
        if but.action == Keyboard.press || but.action == Keyboard.repeat
            if but.key == Keyboard.left
                direction[] = CartesianIndex(-1,0)
            elseif but.key == Keyboard.up
                direction[] = CartesianIndex(0,1)
            elseif but.key == Keyboard.down
                direction[] = CartesianIndex(0,-1)
            elseif but.key == Keyboard.right
                direction[] = CartesianIndex(1,0)
            end
        end
    end

    last_dir = nothing
    while true
        # avoid turn back
        if !isnothing(direction[]) && (isnothing(last_dir) || direction[] != -last_dir)
            last_dir = direction[]
        end
        if !isnothing(last_dir)
            if step!(game[], last_dir)
                game[] = game[]
            else
                break
            end
        end
        sleep(t)
    end
end

play()

Kotlin

Translation of: C++
Works with: Windows 10
// Kotlin Native v0.5

import kotlinx.cinterop.*
import platform.posix.*
import platform.windows.*

const val WID = 60
const val HEI = 30
const val MAX_LEN = 600
const val NUL = '\u0000'

enum class Dir { NORTH, EAST, SOUTH, WEST }

class Snake {
    val console: HANDLE
    var alive = false
    val brd = CharArray(WID * HEI)
    var dir = Dir.NORTH
    val snk = nativeHeap.allocArray<COORD>(MAX_LEN)
    lateinit var head: COORD
    var tailIdx = 0
    var headIdx = 0
    var points = 0

    init {
        console = GetStdHandle(STD_OUTPUT_HANDLE)!!
        SetConsoleTitleW("Snake")
        memScoped {
            val coord = alloc<COORD>().apply { X = (WID + 1).toShort(); Y = (HEI + 2).toShort() }
            SetConsoleScreenBufferSize(console, coord.readValue())
            val rc = alloc<SMALL_RECT>().apply {
                Left = 0; Top = 0; Right = WID.toShort(); Bottom = (HEI + 1).toShort()
            }
            SetConsoleWindowInfo(console, TRUE, rc.ptr)
            val ci = alloc<CONSOLE_CURSOR_INFO>().apply { dwSize = 1; bVisible = FALSE }
            SetConsoleCursorInfo(console, ci.ptr)
        }
    }

    fun play() {
        while (true) {
            createfield()
            alive = true
            while (alive) {
                drawfield()
                readKey()
                moveSnake()
                Sleep(50)
            }
            memScoped {
                val c = alloc<COORD>().apply { X = 0; Y = (HEI + 1).toShort() }
                SetConsoleCursorPosition(console, c.readValue())
            }
            SetConsoleTextAttribute(console, 0x000b)
            print("Play again [Y/N]? ")
            val a = readLine()!!.toLowerCase()
            if (a.length > 0 && a[0] != 'y') {
                nativeHeap.free(snk)
                return
            }
        }
    }

    private fun createfield() {
        memScoped {
            val coord = alloc<COORD>().apply { X = 0; Y = 0 }
            val c = alloc<DWORDVar>()
            FillConsoleOutputCharacterW(console, 32, (HEI + 2) * 80, coord.readValue(), c.ptr)
            FillConsoleOutputAttribute(console, 0x0000, (HEI + 2) * 80, coord.readValue(), c.ptr)
            SetConsoleCursorPosition(console, coord.readValue())
        }
        for (x in 0 until WID * HEI) brd[x] = NUL
        for (x in 0 until WID) {
            brd[x + WID * (HEI - 1)] = '+'
            brd[x] = '+'
        }
        for (y in 1 until HEI) {
            brd[WID - 1 + WID * y] = '+' 
            brd[WID * y] = '+'
        }
        var xx: Int
        var yy: Int
        do {
            xx = rand() % WID
            yy = rand() % (HEI shr 1) + (HEI shr 1)
        }
        while (brd[xx + WID * yy] != NUL)
        brd[xx + WID * yy] = '@'
        tailIdx = 0
        headIdx = 4
        xx = 3
        yy = 2
        for (cc in tailIdx until headIdx) {
            brd[xx + WID * yy] = '#'
            snk[cc].X = (3 + cc).toShort()
            snk[cc].Y = 2
        }
        head = snk[3]
        dir = Dir.EAST
        points = 0
    }

    private fun readKey() {
        if ((GetAsyncKeyState(39).toInt() and 0x8000) != 0) dir = Dir.EAST
        if ((GetAsyncKeyState(37).toInt() and 0x8000) != 0) dir = Dir.WEST
        if ((GetAsyncKeyState(38).toInt() and 0x8000) != 0) dir = Dir.NORTH
        if ((GetAsyncKeyState(40).toInt() and 0x8000) != 0) dir = Dir.SOUTH
    }

    private fun drawfield() {
        memScoped {
            val coord = alloc<COORD>()
            var t = NUL
            for (y in 0 until HEI) {
                coord.Y = y.toShort()
                for (x in 0 until WID) {
                    t = brd[x + WID * y]
                    if (t == NUL) continue
                    coord.X = x.toShort()
                    SetConsoleCursorPosition(console, coord.readValue())
                    if (coord.X == head.X && coord.Y == head.Y) {
                        SetConsoleTextAttribute(console, 0x002e)
                        print('O')
                        SetConsoleTextAttribute(console, 0x0000)
                        continue
                    }
                    when (t) {
                        '#' ->  SetConsoleTextAttribute(console, 0x002a)
                        '+' ->  SetConsoleTextAttribute(console, 0x0019)
                        '@' ->  SetConsoleTextAttribute(console, 0x004c)
                    }
                    print(t)
                    SetConsoleTextAttribute(console, 0x0000)
                }
            }
            print(t)
            SetConsoleTextAttribute(console, 0x0007)
            val c = alloc<COORD>().apply { X = 0; Y = HEI.toShort() }
            SetConsoleCursorPosition(console, c.readValue())
            print("Points: $points")
        }
    }

    private fun moveSnake() {
        when (dir) {
            Dir.NORTH -> head.Y--
            Dir.EAST  -> head.X++
            Dir.SOUTH -> head.Y++
            Dir.WEST  -> head.X--
        }
        val t = brd[head.X + WID * head.Y]
        if (t != NUL && t != '@') {
            alive = false
            return
        }
        brd[head.X + WID * head.Y] = '#'
        snk[headIdx].X = head.X
        snk[headIdx].Y = head.Y
        if (++headIdx >= MAX_LEN) headIdx = 0
        if (t == '@') {
            points++
            var x: Int
            var y: Int
            do {
                x = rand() % WID
                y = rand() % (HEI shr 1) + (HEI shr 1)
            }
            while (brd[x + WID * y] != NUL)
            brd[x + WID * y] = '@'
            return
        }
        SetConsoleCursorPosition(console, snk[tailIdx].readValue())
        print(' ')
        brd[snk[tailIdx].X + WID * snk[tailIdx].Y] = NUL
        if (++tailIdx >= MAX_LEN) tailIdx = 0
    }
}

fun main(args: Array<String>) {
    srand(time(null).toInt())
    Snake().play()
}
Output:
Similar to C++ entry

Lua

File:Snake-lua.png

Works with: LÖVE
UP, RIGHT, DOWN, LEFT = 1, 2, 3, 4
UpdateTime=0.200
Timer = 0
GridSize = 30
GridWidth, GridHeight = 20, 10

local directions = {
	[UP] = 	  {x= 0, y=-1},
	[RIGHT] = {x= 1, y= 0},
	[DOWN] =  {x= 0, y= 1},
	[LEFT] =  {x=-1, y= 0},
}

local function isPositionInBody(x, y)
	for i = 1, #Body-3, 2 do -- skip tail, it moves before we get in
		if x == Body[i] and y == Body[i+1] then
			return true
		end
	end
	return false
end

local function isPositionInApple(x, y)
	if x == Apple.x and y == Apple.y then
		return true
	end
	return false
end

local function newApple ()
	local ApplePlaced = false
	while not ApplePlaced do
		local x = GridSize*math.random (GridWidth)
		local y = GridSize*math.random (GridHeight)
		if not isPositionInBody(x, y) then
			Apple = {x=x, y=y}
			ApplePlaced = true
		end
	end
end

local function newGame ()
	Score = 0
	GameOver = false
	local x = GridSize*math.floor(math.random (0.25*GridWidth, 0.75*GridWidth))
	print (x)
	local y = GridSize*math.floor(math.random (0.25*GridHeight, 0.75*GridHeight))
	print (y)
	local iDirection = math.random(4)
	local d = directions[iDirection]
	Head = {
		x=x,
		y=y,
		iDirection = iDirection,
		nextDirection = iDirection,
	}
	Body = {x, y, x-GridSize*d.x, y-GridSize*d.y}
	Apples = {}
	newApple ()
end

function love.load()
	newGame ()
end

local function moveSnake (x, y, iDirection, longer)
	table.insert (Body, 1, x)
	table.insert (Body, 2, y)
	Head.x = x
	Head.y = y
	Head.iDirection = iDirection
	if not longer then
		-- remove last pair
		table.remove(Body)
		table.remove(Body)
	end
	if  x <= 0 or x > GridSize*(GridWidth) or
		y <= 0 or y > GridSize*(GridHeight) then
		GameOver = true
	end
end
 
function love.update(dt)
	Timer = Timer + dt
	if Timer < UpdateTime then return end
	Timer = Timer - UpdateTime

	local iDirection = Head.nextDirection
	local d = directions[iDirection]
	local x, y = Head.x+GridSize*d.x, Head.y+GridSize*d.y
	if isPositionInBody(x, y) then
		GameOver = true
	elseif isPositionInApple(x, y) then
		Score = Score + 1
		newApple ()
		moveSnake (x, y, iDirection, true)
	else
		moveSnake (x, y, iDirection, false)
	end
end

function drawHead () -- position, length, width and angle
	love.graphics.push()
	love.graphics.translate(Head.x, Head.y)
	love.graphics.rotate((Head.iDirection-2)*math.pi/2)
	love.graphics.polygon("fill", 
		-GridSize/3, -GridSize /3, 
		-GridSize/3,  GridSize /3, 
		 GridSize/3, 0)
	love.graphics.pop() 
end

function love.draw()
	love.graphics.setColor(0,1,0)
	love.graphics.print ('Score: '..tostring(Score), 10, 10)
	if GameOver then
		love.graphics.print ('Game Over: '..tostring(GameOver)..'. Press "Space" to continue', 10, 30)
	else
		love.graphics.translate(GridSize, GridSize)
		love.graphics.setColor(0.6,0.6,0.6)
		love.graphics.setLineWidth(0.25)
		for x = GridSize, GridSize*GridWidth, GridSize do
			love.graphics.line (x, GridSize, x, GridSize*GridHeight)
		end
		for y = GridSize, GridSize*GridHeight, GridSize do
			love.graphics.line (GridSize, y, GridSize*GridWidth, y)
		end
		love.graphics.setLineWidth((GridSize/4)+0.5)
		love.graphics.setColor(1,1,1)
		love.graphics.line (Body)
		drawHead ()
		love.graphics.setColor(1,0,0)
		love.graphics.circle ('fill', Apple.x, Apple.y, GridSize/4)
	end
end

function love.keypressed(key, scancode, isrepeat)
	if false then
	elseif key == "space" then
		if GameOver then
			GameOver = false
			newGame ()
		end
	elseif key == "escape" then
		love.event.quit()
	else
		local iDirection = Head.iDirection
		if iDirection == UP or 
			iDirection == DOWN then
			local right = love.keyboard.isScancodeDown ("d")
			local left = love.keyboard.isScancodeDown ("a")
			if right and not left then
				iDirection = RIGHT
			elseif left and not right then
				iDirection = LEFT
			end
		else -- right or left
			local down = love.keyboard.isScancodeDown ("s")
			local up = love.keyboard.isScancodeDown ("w")
			if up and not down then
				iDirection = UP
			elseif down and not up then
				iDirection = DOWN
			end
		end
		Head.nextDirection = iDirection
	end
end

Nim

Translation of: C
Library: nim-ncurses

As in the C version, the code is provided for Linux. We use the tiny API defined in the C version, only adjusted to work with Nim and the library “nim-ncurses”.

import macros, os, random
import ncurses

when defined(Linux):
  proc positional_putch(x, y: int; ch: char) = mvaddch(x.cint, y.cint, ch.chtype)
  proc updateScreen = refresh()
  proc nonBlockingGetch(): char =
    let c = getch()
    result = if c in 0..255: char(c) else: '\0'
  proc closeScreen = endwin()

else:
  error "Not implemented"

const

  W = 80
  H = 40
  Space = 0
  Food = 1
  Border = 2
  Symbol = [' ', '@', '.']

type

  Dir {.pure.} = enum North, East, South, West
  Game = object
    board: array[W * H, int]
    head: int
    dir: Dir
    quit: bool


proc age(game: var Game) =
  ## Reduce a time-to-live, effectively erasing the tail.
  for i in 0..<W*H:
    if game.board[i] < 0: inc game.board[i]


proc plant(game: var Game) =
  ## Put a piece of food at random empty position.
  var r: int
  while true:
    r = rand(W * H - 1)
    if game.board[r] == Space: break
  game.board[r] = Food


proc start(game: var Game) =
  ## Initialize the board, plant a very first food item.
  for i in 0..<W:
    game.board[i] = Border
    game.board[i + (H - 1) * W] = Border
  for i in 0..<H:
    game.board[i * W] = Border
    game.board[i * W + W - 1] = Border
  game.head = W * (H - 1 - (H and 1)) shr 1   # Screen center for any H.
  game.board[game.head] = -5
  game.dir = North
  game.quit = false
  game.plant()


proc step(game: var Game) =
  let len = game.board[game.head]
  case game.dir
  of North: dec game.head, W
  of South: inc game.head, W
  of West: dec game.head
  of East: inc game.head

  case game.board[game.head]
  of Space:
    game.board[game.head] = len - 1  # Keep in mind "len" is negative.
    game.age()
  of Food:
    game.board[game.head] = len - 1
    game.plant()
  else:
    game.quit = true


proc show(game: Game) =
  for i in 0..<W*H:
    positionalPutch(i div W, i mod W, if game.board[i] < 0: '#' else: Symbol[game.board[i]])
  updateScreen()


var game: Game
randomize()
let win = initscr()
cbreak()              # Make sure thre is no buffering.
noecho()              # Suppress echoing of characters.
nodelay(win, true)    # Non-blocking mode.
game.start()

while not game.quit:
  game.show()
  case nonBlockingGetch()
  of 'i': game.dir = North
  of 'j': game.dir = West
  of 'k': game.dir = South
  of 'l': game.dir = East
  of 'q': game.quit = true
  else: discard
  game.step()
  os.sleep(300)       # Adjust here: 100 is very fast.

sleep(1000)
closeScreen()

OCaml

Library: OCamlSDL2
(* A simple Snake Game *)
open Sdl

let width, height = (640, 480)

type pos = int * int

type game_state = {
  pos_snake: pos;
  seg_snake: pos list;
  dir_snake: [`left | `right | `up | `down];
  pos_fruit: pos;
  sleep_time: int;
  game_over: bool;
}

let red   = (255, 0, 0)
let blue  = (0, 0, 255)
let green = (0, 255, 0)
let black = (0, 0, 0)
let alpha = 255

let fill_rect renderer (x, y) =
  let rect = Rect.make4 x y 20 20 in
  Render.fill_rect renderer rect;
;;


let display_game renderer state =
  let bg_color, snake_color, fruit_color =
    if state.game_over
    then (red, black, green)
    else (black, blue, red)
  in
  Render.set_draw_color renderer bg_color alpha;
  Render.clear renderer;
  Render.set_draw_color renderer fruit_color alpha;
  fill_rect renderer state.pos_fruit;
  Render.set_draw_color renderer snake_color alpha;
  List.iter (fill_rect renderer) state.seg_snake;
  Render.render_present renderer;
;;


let proc_events dir_snake = function
  | Event.KeyDown { Event.keycode = Keycode.Left } -> `left
  | Event.KeyDown { Event.keycode = Keycode.Right } -> `right
  | Event.KeyDown { Event.keycode = Keycode.Up } -> `up
  | Event.KeyDown { Event.keycode = Keycode.Down } -> `down
  | Event.KeyDown { Event.keycode = Keycode.Q }
  | Event.KeyDown { Event.keycode = Keycode.Escape }
  | Event.Quit _ -> Sdl.quit (); exit 0
  | _ -> (dir_snake)


let rec event_loop dir_snake =
  match Event.poll_event () with
  | None -> (dir_snake)
  | Some ev ->
      let dir = proc_events dir_snake ev in
      event_loop dir


let rec pop = function
  | [_] -> []
  | hd :: tl -> hd :: (pop tl)
  | [] -> invalid_arg "pop"


let rec new_pos_fruit seg_snake =
  let new_pos =
    (20 * Random.int 32,
     20 * Random.int 24)
  in
  if List.mem new_pos seg_snake
  then new_pos_fruit seg_snake
  else (new_pos)


let update_state req_dir ({
    pos_snake;
    seg_snake;
    pos_fruit;
    dir_snake;
    sleep_time;
    game_over;
  } as state) =
  if game_over then state else
  let dir_snake =
    match dir_snake, req_dir with
    | `left, `right -> dir_snake
    | `right, `left -> dir_snake
    | `up, `down -> dir_snake
    | `down, `up -> dir_snake
    | _ -> req_dir
  in
  let pos_snake =
    let x, y = pos_snake in
    match dir_snake with
    | `left  -> (x - 20, y)
    | `right -> (x + 20, y)
    | `up    -> (x, y - 20)
    | `down  -> (x, y + 20)
  in
  let game_over =
    let x, y = pos_snake in
    List.mem pos_snake seg_snake
    || x < 0 || y < 0
    || x >= width
    || y >= height
  in
  let seg_snake = pos_snake :: seg_snake in
  let seg_snake, pos_fruit, sleep_time =
    if pos_snake = pos_fruit
    then (seg_snake, new_pos_fruit seg_snake, sleep_time - 1)
    else (pop seg_snake, pos_fruit, sleep_time)
  in
  { pos_snake;
    seg_snake;
    pos_fruit;
    dir_snake;
    sleep_time;
    game_over;
  }


let () =
  Random.self_init ();
  Sdl.init [`VIDEO];
  let window, renderer =
    Render.create_window_and_renderer ~width ~height ~flags:[]
  in
  Window.set_title ~window ~title:"Snake OCaml-SDL2";
  let initial_state = {
    pos_snake = (100, 100);
    seg_snake = [
      (100, 100);
      ( 80, 100);
      ( 60, 100);
    ];
    pos_fruit = (200, 200);
    dir_snake = `right;
    sleep_time = 120;
    game_over = false;
  } in

  let rec main_loop state =
    let req_dir = event_loop state.dir_snake in
    let state = update_state req_dir state in
    display_game renderer state;
    Timer.delay state.sleep_time;
    main_loop state
  in
  main_loop initial_state

Perl

use utf8;
use Time::HiRes qw(sleep);
use Term::ANSIColor qw(colored);
use Term::ReadKey qw(ReadMode ReadLine);

binmode(STDOUT, ':utf8');

use constant {
              VOID => 0,
              HEAD => 1,
              BODY => 2,
              TAIL => 3,
              FOOD => 4,
             };

use constant {
              LEFT  => [+0, -1],
              RIGHT => [+0, +1],
              UP    => [-1, +0],
              DOWN  => [+1, +0],
             };

use constant {
              BG_COLOR  => "on_black",
              SLEEP_SEC => 0.05,
             };

use constant {
              SNAKE_COLOR => ('bold green' . ' ' . BG_COLOR),
              FOOD_COLOR  => ('red'        . ' ' . BG_COLOR),
             };

use constant {
    U_HEAD => colored('▲', SNAKE_COLOR),
    D_HEAD => colored('▼', SNAKE_COLOR),
    L_HEAD => colored('◀', SNAKE_COLOR),
    R_HEAD => colored('▶', SNAKE_COLOR),

    U_BODY => colored('╹', SNAKE_COLOR),
    D_BODY => colored('╻', SNAKE_COLOR),
    L_BODY => colored('╴', SNAKE_COLOR),
    R_BODY => colored('╶', SNAKE_COLOR),

    U_TAIL => colored('╽', SNAKE_COLOR),
    D_TAIL => colored('╿', SNAKE_COLOR),
    L_TAIL => colored('╼', SNAKE_COLOR),
    R_TAIL => colored('╾', SNAKE_COLOR),

    A_VOID => colored(' ',   BG_COLOR),
    A_FOOD => colored('❇', FOOD_COLOR),
             };

local $| = 1;

my $w = eval { `tput cols` }  || 80;
my $h = eval { `tput lines` } || 24;
my $r = "\033[H";

my @grid = map {
    [map { [VOID] } 1 .. $w]
} 1 .. $h;

my $dir      = LEFT;
my @head_pos = ($h / 2, $w / 2);
my @tail_pos = ($head_pos[0], $head_pos[1] + 1);

$grid[$head_pos[0]][$head_pos[1]] = [HEAD, $dir];    # head
$grid[$tail_pos[0]][$tail_pos[1]] = [TAIL, $dir];    # tail

sub create_food {
    my ($food_x, $food_y);

    do {
        $food_x = rand($w);
        $food_y = rand($h);
    } while ($grid[$food_y][$food_x][0] != VOID);

    $grid[$food_y][$food_x][0] = FOOD;
}

create_food();

sub display {
    my $i = 0;

    print $r, join("\n",
        map {
            join("",
                map {
                    my $t = $_->[0];
                    if ($t != FOOD and $t != VOID) {
                        my $p = $_->[1];
                        $i =
                            $p eq UP   ? 0
                          : $p eq DOWN ? 1
                          : $p eq LEFT ? 2
                          :              3;
                    }
                        $t == HEAD ? (U_HEAD, D_HEAD, L_HEAD, R_HEAD)[$i]
                      : $t == BODY ? (U_BODY, D_BODY, L_BODY, R_BODY)[$i]
                      : $t == TAIL ? (U_TAIL, D_TAIL, L_TAIL, R_TAIL)[$i]
                      : $t == FOOD ? (A_FOOD)
                      :              (A_VOID);

                  } @{$_}
                )
          } @grid
    );
}

sub move {
    my $grew = 0;

    # Move the head
    {
        my ($y, $x) = @head_pos;

        my $new_y = ($y + $dir->[0]) % $h;
        my $new_x = ($x + $dir->[1]) % $w;

        my $cell = $grid[$new_y][$new_x];
        my $t    = $cell->[0];

        if ($t == BODY or $t == TAIL) {
            die "Game over!\n";
        }
        elsif ($t == FOOD) {
            create_food();
            $grew = 1;
        }

        # Create a new head
        $grid[$new_y][$new_x] = [HEAD, $dir];

        # Replace the current head with body
        $grid[$y][$x] = [BODY, $dir];

        # Save the position of the head
        @head_pos = ($new_y, $new_x);
    }

    # Move the tail
    if (not $grew) {
        my ($y, $x) = @tail_pos;

        my $pos   = $grid[$y][$x][1];
        my $new_y = ($y + $pos->[0]) % $h;
        my $new_x = ($x + $pos->[1]) % $w;

        $grid[$y][$x][0]         = VOID;    # erase the current tail
        $grid[$new_y][$new_x][0] = TAIL;    # create a new tail

        # Save the position of the tail
        @tail_pos = ($new_y, $new_x);
    }
}

ReadMode(3);
while (1) {
    my $key;
    until (defined($key = ReadLine(-1))) {
        move();
        display();
        sleep(SLEEP_SEC);
    }

    if    ($key eq "\e[A" and $dir ne DOWN ) { $dir = UP    }
    elsif ($key eq "\e[B" and $dir ne UP   ) { $dir = DOWN  }
    elsif ($key eq "\e[C" and $dir ne LEFT ) { $dir = RIGHT }
    elsif ($key eq "\e[D" and $dir ne RIGHT) { $dir = LEFT  }
}

Phix

Translation of: C++
constant W = 60, H = 30, MAX_LEN = 600
enum NORTH, EAST, SOUTH, WEST

sequence board, snake
bool alive
integer tailIdx, headIdx, hdX, hdY, d, points
 
procedure createField()
    clear_screen()
    board = repeat("+"&repeat(' ',W-2)&'+',H)
    for x=1 to W do
        board[1,x] = '+'
    end for
    board[H] = board[1]
    board[1+rand(H-2),1+rand(W-2)] = '@';
    snake = repeat(0,MAX_LEN)
    board[3,4] = '#'; tailIdx = 1; headIdx = 5;
    for c=tailIdx to headIdx do
        snake[c] = {3,3+c}
    end for
    {hdY,hdX} = snake[headIdx-1]; d = EAST; points = 0;
end procedure

procedure drawField()
    for y=1 to H do
        for x=1 to W do
            integer t = board[y,x]
            if t!=' ' then
                position(y,x)
                if x=hdX and y=hdY then
                    text_color(14); puts(1,'O');
                else
                    text_color({10,9,12}[find(t,"#+@")]); puts(1,t);
                end if
            end if
        end for
    end for
    position(H+1,1); text_color(7); printf(1,"Points: %d",points)
end procedure

procedure readKey()
    integer k = find(get_key(),{333,331,328,336})
    if k then d = {EAST,WEST,NORTH,SOUTH}[k] end if
end procedure

procedure moveSnake()
integer x,y
    switch d do
        case NORTH: hdY -= 1
        case EAST:  hdX += 1
        case SOUTH: hdY += 1
        case WEST:  hdX -= 1
    end switch
    integer t = board[hdY,hdX];
    if t!=' ' and t!='@' then alive = false; return; end if
    board[hdY,hdX] = '#'; snake[headIdx] = {hdY,hdX};
    headIdx += 1; if headIdx>MAX_LEN then headIdx = 1 end if
    if t=='@' then
        points += 1
        while 1 do
            x = 1+rand(W-2); y = 1+rand(H-2);
            if board[y,x]=' ' then
                board[y,x] = '@'
                return
            end if
        end while
    end if
    {y,x} = snake[tailIdx]; position(y,x); puts(1,' '); board[y,x] = ' ';
    tailIdx += 1; if tailIdx>MAX_LEN then tailIdx = 1 end if
end procedure

procedure play()
    while true do
        createField(); alive = true; cursor(NO_CURSOR)
        while alive do drawField(); readKey(); moveSnake(); sleep(0.05) end while
        cursor(BLOCK_CURSOR); position(H+2,1); bk_color(0); text_color(11);
        puts(1,"Play again [Y/N]? ")
        if upper(wait_key())!='Y' then return end if
    end while
end procedure
play()

Python

Using Pygame. Works with Python >= 3.7.

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:
        # Move the snake in the direction it is facing.
        self.rect.move_ip(
            (
                self.facing[0] * self.speed,
                self.facing[1] * self.speed,
            )
        )

        # Move to the opposite side of the screen if the snake goes out of bounds.
        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()

        # Snake body sprites don't update themselves. We update them here.
        segments = self.sprites()
        for i in range(len(segments) - 1, 0, -1):
            # Current sprite takes the position of the previous sprite.
            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)

        # XXX: This approach to random food placement might end badly if the
        # snake is very large.
        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

            # Change direction using the arrow keys.
            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)

            # Detect collisions after update.
            self.update(screen)

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

                # Increase framerate to speed up gameplay.
                if self.score % 5 == 0:
                    self.framerate += 1

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

            # Snake hit its own tail.
            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)

Raku

(formerly Perl 6)

Works with: Rakudo version 2016.08

This is a variation of a demo script included in the examples folder for the Raku SDL2::Raw library bindings.

use SDL2::Raw;
use Cairo;

constant W = 1280;
constant H = 960;

constant FIELDW = W div 32;
constant FIELDH = H div 32;

SDL_Init(VIDEO);

my $window = SDL_CreateWindow(
    'Snake',
    SDL_WINDOWPOS_CENTERED_MASK,
    SDL_WINDOWPOS_CENTERED_MASK,
    W, H,
    OPENGL
);

my $render = SDL_CreateRenderer($window, -1, ACCELERATED +| PRESENTVSYNC);

my $snake_image = Cairo::Image.record(
    -> $_ {
        .save;
        .rectangle: 0, 0, 64, 64;
        .clip;
        .rgb: 0, 1, 0;
        .rectangle: 0, 0, 64, 64;
        .fill :preserve;
        .rgb: 0, 0, 0;
        .stroke;
        .restore;

        .save;
        .translate: 64, 0;
        .rectangle: 0, 0, 64, 64;
        .clip;
        .rgb: 1, 0, 0;
        .arc: 32, 32, 30, 0, 2 * pi;
        .fill :preserve;
        .rgb: 0, 0, 0;
        .stroke;
        .restore;
    }, 128, 128, Cairo::FORMAT_ARGB32);

my $snake_texture = SDL_CreateTexture(
    $render,
    %PIXELFORMAT<ARGB8888>,
    STATIC,
    128,
    128
);

SDL_UpdateTexture(
    $snake_texture,
    SDL_Rect.new(
        :x(0),
        :y(0),
        :w(128),
        :h(128)
    ),
    $snake_image.data,
    $snake_image.stride // 128 * 4
);

SDL_SetTextureBlendMode($snake_texture, 1);

SDL_SetRenderDrawBlendMode($render, 1);

my $snakepiece_srcrect = SDL_Rect.new(:w(64), :h(64));
my $nompiece_srcrect   = SDL_Rect.new(:w(64), :h(64), :x(64));

my $event = SDL_Event.new;

enum GAME_KEYS (
    K_UP    => 82,
    K_DOWN  => 81,
    K_LEFT  => 80,
    K_RIGHT => 79,
);

my Complex @snakepieces = 10+10i;
my Complex @noms;
my Complex $snakedir = 1+0i;
my $nomspawn   = 0;
my $snakespeed = 0.1;
my $snakestep  = 0;
my $nom        = 4;

my $last_frame_start = now;
main: loop {
    my $start = now;
    my $dt = $start - $last_frame_start // 0.00001;
    while SDL_PollEvent($event) {
        my $casted_event = SDL_CastEvent($event);
        given $casted_event {
            when *.type == QUIT    { last main }
            when *.type == KEYDOWN {
                if GAME_KEYS(.scancode) -> $comm {
                    given $comm {
                        when 'K_LEFT'  { $snakedir = -1+0i unless $snakedir ==  1+0i }
                        when 'K_RIGHT' { $snakedir =  1+0i unless $snakedir == -1+0i }
                        when 'K_UP'    { $snakedir =  0-1i unless $snakedir ==  0+1i }
                        when 'K_DOWN'  { $snakedir =  0+1i unless $snakedir ==  0-1i }
                    }
                }
            }
        }
    }

    if ($nomspawn -= $dt) < 0 {
        $nomspawn += 1;
        @noms.push: (^FIELDW).pick + (^FIELDH).pick * i unless @noms > 3;
        @noms.pop if @noms[*-1] == any(@snakepieces);
    }

    if ($snakestep -= $dt) < 0 {
        $snakestep += $snakespeed;

        @snakepieces.unshift: do given @snakepieces[0] {
            ($_.re + $snakedir.re) % FIELDW
            + (($_.im + $snakedir.im) % FIELDH) * i
        }

        if @snakepieces[2..*].first( * == @snakepieces[0], :k ) -> $idx {
            @snakepieces = @snakepieces[0..($idx + 1)];
        }

        @noms .= grep(
            { $^piece == @snakepieces[0] ?? ($nom += 1) && False !! True }
        );

        if $nom == 0 {
            @snakepieces.pop;
        } else {
            $nom = $nom - 1;
        }
    }

    for @snakepieces {
        SDL_SetTextureColorMod(
            $snake_texture,
            255,
            (cos((++$) / 2) * 100 + 155).round,
            255
        );

        SDL_RenderCopy(
            $render,
            $snake_texture,
            $snakepiece_srcrect,
            SDL_Rect.new(.re * 32, .im * 32, 32, 32)
        );
    }

    SDL_SetTextureColorMod($snake_texture, 255, 255, 255);

    for @noms {
        SDL_RenderCopy(
            $render,
            $snake_texture,
            $nompiece_srcrect,
            SDL_Rect.new(.re * 32, .im * 32, 32, 32)
        )
    }

    SDL_RenderPresent($render);
    SDL_SetRenderDrawColor($render, 0, 0, 0, 0);
    SDL_RenderClear($render);

    $last_frame_start = $start;
    sleep(1 / 50);
}

SDL_Quit();

Rust

Implemented smooth (per-pixel) animation on Win32 API (tested on Windows 7 and Windows 11)

/* add to file Cargo.toml:
[dependencies]
winsafe = "0.0.8" # IMHO: before the appearance of winsafe="0.1" it is not worth raising the version here
rand = "0.8"
derive-new = "0.5"
*/

#![windows_subsystem = "windows"]

use derive_new::new;
use rand::{thread_rng, Rng};
use std::{cell::RefCell, rc::Rc};
use winsafe::{co, gui, prelude::*, COLORREF, HBRUSH, HPEN, RECT, SIZE};

const STEP: i32 = 3; // px, motion per frame
const GCW: i32 = 7; // game grid cell width in STEPs
const SNAKE_W: i32 = 20; // px
const FW: i32 = 20; // the width of the square field in the cells of the game grid
const TW: i32 = (FW + 2) * GCW; // total field width (with overlap for collisions) in STEPs
const ID0: i32 = FW / 2 * GCW; // starting position id

#[rustfmt::skip]
#[derive(new)]
struct Context {
                               wnd: gui::WindowMain,
    #[new(default)           ] snake: Vec<i32>, // [rect_ids] where rect_id = y * TW + x (where x, y: nSTEPs)
    #[new(value = "[ID0; 6]")] r: [i32; 6], // ID 6 rect to color in next frame (bg, tail, turn, body, food, head)
    #[new(default)           ] incr: i32, // 0 | -1 | 1 | -TW | TW - increment r[head] in next STEP
    #[new(value = "TW")      ] next_incr: i32, // `incr` in the next grid cell
    #[new(default)           ] gap: i32, // interval in STEPs to the next grid cell; negative - tail clipping mark
}

pub fn main() {
    let [bg, tail, turn, body, food, head] = [0_usize, 1, 2, 3, 4, 5];
    let grid: Vec<_> = (1..=FW).flat_map(|y| (1..=FW).map(move |x| (y * TW + x) * GCW)).collect();
    let mut colors = [(0x00, 0xF0, 0xA0); 6]; // color tail, turn, body
    colors[bg] = (0x00, 0x50, 0x90);
    colors[food] = (0xFF, 0x50, 0x00);
    colors[head] = (0xFF, 0xFF, 0x00);
    let brushes = COLORREF::new_array(&colors).map(|c| HBRUSH::CreateSolidBrush(c).unwrap());

    let wnd = gui::WindowMain::new(gui::WindowMainOpts {
        title: "Snake - Start: Space, then press W-A-S-D".to_string(),
        size: SIZE::new(FW * GCW * STEP, FW * GCW * STEP),
        class_bg_brush: brushes[bg],
        ..Default::default()
    });
    // WindowMain is based on Arc, so wnd.clone() is a shallow copy of a reference.
    let context = Rc::new(RefCell::new(Context::new(wnd.clone())));

    wnd.on().wm_paint({
        let context = Rc::clone(&context);
        move || {
            let ctx = context.borrow();
            let mut ps = winsafe::PAINTSTRUCT::default();
            let hdc = ctx.wnd.hwnd().BeginPaint(&mut ps)?;
            hdc.SelectObjectPen(HPEN::CreatePen(co::PS::NULL, 0, COLORREF::new(0, 0, 0))?)?;
            for (&id, &brush) in ctx.r.iter().zip(&brushes[bg..=head]) {
                let [left, top] = [id % TW, id / TW].map(|i| i * STEP - (STEP * GCW + SNAKE_W) / 2);
                let rect = RECT { left, top, right: left + SNAKE_W, bottom: top + SNAKE_W };
                hdc.SelectObjectBrush(brush)?;
                hdc.RoundRect(rect, SIZE::new(SNAKE_W / 2, SNAKE_W / 2))?;
            }
            Ok(ctx.wnd.hwnd().EndPaint(&ps))
        }
    });

    wnd.on().wm_key_down({
        let context = Rc::clone(&context);
        move |key| {
            let mut ctx = context.borrow_mut();
            Ok(match (ctx.incr.abs(), key.char_code as u8) {
                (0, b' ') => _ = ctx.wnd.hwnd().SetTimer(1, 10, None)?, // Start / Restart
                (TW, bt @ (b'A' | b'D')) => ctx.next_incr = if bt == b'A' { -1 } else { 1 },
                (1, bt @ (b'S' | b'W')) => ctx.next_incr = if bt == b'S' { TW } else { -TW },
                _ => (),
            })
        }
    });

    wnd.on().wm_timer(1, move || {
        let mut ctx = context.borrow_mut();
        let new_h = ctx.r[head] + ctx.incr;
        (ctx.r[body], ctx.r[head]) = (ctx.r[head], new_h);
        if ctx.gap < 0 {
            ctx.r[bg] = ctx.snake.remove(0);
            ctx.r[tail] = ctx.snake[0];
            ctx.r[turn] = ctx.snake[GCW as usize / 2];
        }
        ctx.gap -= ctx.gap.signum();
        if ctx.gap == 0 {
            ctx.gap = if new_h == ctx.r[food] { GCW } else { -GCW };
            let mut snake_cells: Vec<_> = ctx.snake.iter().step_by(GCW as usize).collect();
            if new_h == ctx.r[food] {
                ctx.wnd.hwnd().SetWindowText(&format!("Snake - Eaten: {}", snake_cells.len()))?;
                snake_cells.sort();
                ctx.r[food] = *(grid.iter())
                    .filter(|i| **i != new_h && snake_cells.binary_search(i).is_err())
                    .nth(thread_rng().gen_range(0..(grid.len() - 1 - snake_cells.len()).max(1)))
                    .unwrap_or(&0);
            } else if grid.binary_search(&new_h).is_err() || snake_cells.contains(&&new_h) {
                ctx.wnd.hwnd().KillTimer(1)?; // Stop
                let title = ctx.wnd.hwnd().GetWindowText()?;
                ctx.wnd.hwnd().SetWindowText(&(title + ". Restart: Space"))?;
                *ctx = Context::new(ctx.wnd.clone());
                return Ok(());
            }
            ctx.incr = ctx.next_incr;
        }
        ctx.snake.push(new_h);
        ctx.wnd.hwnd().InvalidateRect(None, new_h == ID0)?; // call .wm_paint(), with erase on Restart
        Ok(())
    });

    wnd.run_main(None).unwrap();
}
Output:

Sidef

class SnakeGame(w, h) {
    const readkey = frequire('Term::ReadKey')
    const ansi    = frequire('Term::ANSIColor')

    enum (VOID, HEAD, BODY, TAIL, FOOD)

    define (
        LEFT  = [+0, -1],
        RIGHT = [+0, +1],
        UP    = [-1, +0],
        DOWN  = [+1, +0],
    )

    define BG_COLOR    = "on_black"
    define FOOD_COLOR  = ("red"        + " " + BG_COLOR)
    define SNAKE_COLOR = ("bold green" + " " + BG_COLOR)
    define SLEEP_SEC   = 0.02

    const (
        A_VOID  = ansi.colored(' ', BG_COLOR),
        A_FOOD  = ansi.colored('❇', FOOD_COLOR),
        A_BLOCK = ansi.colored('■', SNAKE_COLOR),
    )

    has dir = LEFT
    has grid = [[]]
    has head_pos = [0, 0]
    has tail_pos = [0, 0]

    method init {
        grid = h.of { w.of { [VOID] } }

        head_pos = [h//2, w//2]
        tail_pos = [head_pos[0], head_pos[1]+1]

        grid[head_pos[0]][head_pos[1]] = [HEAD, dir]    # head
        grid[tail_pos[0]][tail_pos[1]] = [TAIL, dir]    # tail

        self.make_food()
    }

    method make_food {
        var (food_x, food_y)

        do {
            food_x = w.rand.int
            food_y = h.rand.int
        } while (grid[food_y][food_x][0] != VOID)

        grid[food_y][food_x][0] = FOOD
    }

    method display {
        print("\033[H", grid.map { |row|
            row.map { |cell|
                given (cell[0]) {
                    when (VOID) { A_VOID }
                    when (FOOD) { A_FOOD }
                    default     { A_BLOCK }
                }
              }.join('')
            }.join("\n")
        )
    }

    method move {
        var grew = false

        # Move the head
        var (y, x) = head_pos...

        var new_y = (y+dir[0] % h)
        var new_x = (x+dir[1] % w)

        var cell = grid[new_y][new_x]

        given (cell[0]) {
            when (BODY) { die "\nYou just bit your own body!\n" }
            when (TAIL) { die "\nYou just bit your own tail!\n" }
            when (FOOD) { grew = true; self.make_food()         }
        }

        # Create a new head
        grid[new_y][new_x] = [HEAD, dir]

        # Replace the current head with body
        grid[y][x] = [BODY, dir]

        # Update the head position
        head_pos = [new_y, new_x]

        # Move the tail
        if (!grew) {
            var (y, x) = tail_pos...

            var pos   = grid[y][x][1]
            var new_y = (y+pos[0] % h)
            var new_x = (x+pos[1] % w)

            grid[y][x][0]         = VOID    # erase the current tail
            grid[new_y][new_x][0] = TAIL    # create a new tail

            tail_pos = [new_y, new_x]
        }
    }

    method play {
        STDOUT.autoflush(true)
        readkey.ReadMode(3)

        try {
            loop {
                var key
                while (!defined(key = readkey.ReadLine(-1))) {
                    self.move()
                    self.display()
                    Sys.sleep(SLEEP_SEC)
                }

                given (key) {
                    when ("\e[A") { if (dir != DOWN ) { dir = UP    } }
                    when ("\e[B") { if (dir != UP   ) { dir = DOWN  } }
                    when ("\e[C") { if (dir != LEFT ) { dir = RIGHT } }
                    when ("\e[D") { if (dir != RIGHT) { dir = LEFT  } }
                }
            }
        }
        catch {
            readkey.ReadMode(0)
        }
    }
}

var w = `tput cols`.to_i
var h = `tput lines`.to_i

SnakeGame(w || 80, h || 24).play

UNIX Shell

Works with: Bourne Again SHell
Works with: Z Shell

Play ASCII snake in your ANSI terminal.

It theoretically should also work with the Korn Shell, but for some reason the timeout on the key read is not consistent and the snake stops moving except one step after each keypress.

function main {
  typeset -i game_over=0
  typeset -i height=$(tput lines) width=$(tput cols)

  # start out in the middle moving to the right
  typeset -i dx dy hx=$(( width/2 )) hy=$(( height/2 ))
  typeset -a sx=($hx) sy=($hy)
  typeset -a timeout
  clear
  tput cup "$sy" "$sx" && printf '@'
  tput cup $(( height/2+2 )) 0
  center $width "Press h, j, k, l to move left, down, up, right"

  # place first food
  typeset -i fx=hx fy=hy
  while (( fx == hx && fy == hy )); do
    fx=$(( RANDOM % (width-2)+1 )) fy=$(( RANDOM % (height-2)+1 ))
  done
  tput cup "$fy" "$fx" && printf '*'

  # handle variations between shells
  keypress=(-N 1) origin=0
  if [[ -n $ZSH_VERSION ]]; then
    keypress=(-k)
    origin=1
  fi

  stty -echo
  tput civis
  typeset key
  read "${keypress[@]}" -s key
  typeset -i start_time=$(date +%s)
  tput cup "$(( height/2+2 ))" 0 && tput el
  while (( ! game_over )); do
    timeout=(-t $(printf '0.%04d' $(( 2000 / (${#sx[@]}+1) )) ) )
    if [[ -z $key ]]; then
      read "${timeout[@]}" "${keypress[@]}" -s key
    fi

    case "$key" in
      h) if (( dx !=  1 )); then dx=-1; dy=0; fi;;
      j) if (( dy != -1 )); then dy=1;  dx=0; fi;;
      k) if (( dy !=  1 )); then dy=-1; dx=0; fi;;
      l) if (( dx != -1 )); then dx=1;  dy=0; fi;;
      q) game_over=1; tput cup 0 0 && print "Final food was at ($fx,$fy)";;
    esac
    key=
    (( hx += dx, hy += dy ))
    # if we try to go off screen, game over
    if (( hx < 0 || hx >= width || hy < 0 || hy >= height )); then
       game_over=1
    else
      # if we run into ourself, game over
      for (( i=0; i<${#sx[@]}; ++i )); do
        if (( hx==sx[i+origin] && hy==sy[i+origin] )); then
          game_over=1
          break
        fi
      done
    fi
    if (( game_over )); then
       break
    fi
    # add new spot
    sx+=($hx) sy+=($hy)

    if (( hx == fx  && hy == fy )); then
      # if we just ate some food, place some new food out
      ok=0
      while  (( ! ok )); do
        # make sure we don't put it under ourselves
        ok=1
        fx=$(( RANDOM % (width-2)+1 )) fy=$(( RANDOM % (height-2)+1 ))
        for (( i=0; i<${#sx[@]}; ++i )); do
          if (( fx == sx[i+origin] && fy == sy[i+origin] )); then
            ok=0
            break
          fi
        done
      done
      tput cup "$fy" "$fx" && printf '*'
      # and don't remove our tail because we've just grown by 1
    else
      # if we didn't just eat food, remove our tail from its previous spot
      tput cup ${sy[origin]} ${sx[origin]} && printf ' '
      sx=( ${sx[@]:1} )
      sy=( ${sy[@]:1} )
    fi
    # draw our new head
    tput cup "$hy" "$hx" && printf  '@'
  done
  typeset -i end_time=$(date +%s)
  tput cup $(( height / 2 -1 )) 0 && center $width 'GAME OVER'
  tput cup $(( height / 2 ))  0 &&
      center $width 'Time: %d seconds' $(( end_time - start_time ))
  tput cup $(( height / 2 + 1 )) 0 &&
      center $width 'Final length: %d' ${#sx[@]}
  echo
  stty echo
  tput cnorm
}

function center {
  typeset -i width=$1 i
  shift
  typeset message=$(printf "$@")
  tput cuf $(( (width-${#message}) / 2 ))
  printf '%s' "$message"
}

main "$@"

Wren

Translation of: C
Library: ncurses
Library: Wren-dynamic

An embedded program so we can ask the C host to call ncurses and another library function for us.

/* Snake.wren */

import "random" for Random
import "./dynamic" for Enum, Lower

foreign class Window {
    construct initscr() {}

    foreign nodelay(bf)
}

class Ncurses {
    foreign static cbreak()

    foreign static noecho()

    foreign static refresh()

    foreign static getch()

    foreign static mvaddch(y, x, ch)

    foreign static endwin()
}

class C {
    foreign static usleep(usec)
}

var Dir = Enum.create("Dir", ["N", "E", "S", "W"])
var State = Enum.create("State", ["space", "food", "border"])

var w = 80
var h = 40
var board = List.filled(w * h, 0)
var rand = Random.new()
var head
var dir
var quit

// ASCII values
var hash = 35
var at   = 64
var dot  = 46
var spc  = 32

/* negative values denote the snake (a negated time-to-live in given cell) */

// reduce a time-to-live, effectively erasing the tail
var age = Fn.new {
    for (i in 0...w * h) {
        if (board[i] < 0) board[i] = board[i] + 1
    }
}

// put a piece of food at random empty position
var plant = Fn.new {
    var r
    while (true) {
        r = rand.int(w * h)
        if (board[r] = State.space) break
    }
    board[r] = State.food
}

// initialize the board, plant a very first food item
var start = Fn.new {
    for (i in 0...w) board[i] = board[i + (h - 1) * w] = State.border
    for (i in 0...h) board[i * w] = board[i * w + w - 1] = State.border
    head = (w * (h - 1 - h % 2) / 2).floor  // screen center for any h
    board[head] = -5
    dir = Dir.N
    quit = false
    plant.call()
}

var step = Fn.new {
    var len = board[head]
    if (dir == Dir.N) {
        head = head - w
    } else if (dir == Dir.S) {
        head = head + w
    } else if (dir == Dir.W) {
        head = head - 1
    } else if (dir == Dir.E) {
        head =  head + 1
    }

    if (board[head] == State.space) {
        board[head] = len - 1  // keep in mind len is negative
        age.call()
    } else if (board[head] == State.food) {
        board[head] = len - 1
        plant.call()
    } else {
        quit = true
    }
}

var show = Fn.new {
    var symbol = [spc, at, dot]
    for (i in 0...w*h) {
        Ncurses.mvaddch((i/w).floor, i % w, board[i] < 0 ? hash : symbol[board[i]])
    }
    Ncurses.refresh()
}

var initScreen = Fn.new {
    var win = Window.initscr()
    Ncurses.cbreak()
    Ncurses.noecho()
    win.nodelay(true)
}

initScreen.call()
start.call()
while (true) {
    show.call()
    var ch = Ncurses.getch()
    if (ch == Lower.i) {
        dir = Dir.N
    } else if (ch == Lower.j) {
        dir = Dir.W
    } else if (ch == Lower.k) {
        dir = Dir.S
    } else if (ch == Lower.l) {
        dir = Dir.E
    } else if (ch == Lower.q) {
        quit = true
    }
    step.call()
    C.usleep(300 * 1000)  // 300 ms is a reasonable delay
    if (quit) break
}
C.usleep(999 * 1000)
Ncurses.endwin()


Now embed this script in the following C program, compile and run it.

/* gcc Snake.c -o Snake -lncurses -lwren -lm */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ncurses.h>
#include <unistd.h>
#include "wren.h"

/* C <=> Wren interface functions */

void C_windowAllocate(WrenVM* vm) {
    WINDOW** pwin = (WINDOW**)wrenSetSlotNewForeign(vm, 0, 0, sizeof(WINDOW*));
    *pwin = initscr();
}

void C_nodelay(WrenVM* vm) {
    WINDOW* win = *(WINDOW**)wrenGetSlotForeign(vm, 0);
    bool bf = wrenGetSlotBool(vm, 1);
    nodelay(win, bf);
}

void C_cbreak(WrenVM* vm) {
    cbreak();
}

void C_noecho(WrenVM* vm) {
    noecho();
}

void C_refresh(WrenVM* vm) {
    refresh();
}

void C_getch(WrenVM* vm) {
    int ch = getch();
    wrenSetSlotDouble(vm, 0, (double)ch);
}

void C_mvaddch(WrenVM* vm) {
    int y = (int)wrenGetSlotDouble(vm, 1);
    int x = (int)wrenGetSlotDouble(vm, 2);
    const chtype ch = (const chtype)wrenGetSlotDouble(vm, 3);
    mvaddch(y, x, ch);
}

void C_endwin(WrenVM* vm) {
    endwin();
}

void C_usleep(WrenVM* vm) {
    useconds_t usec = (useconds_t)wrenGetSlotDouble(vm, 1);
    usleep(usec);
}

WrenForeignClassMethods bindForeignClass(WrenVM* vm, const char* module, const char* className) {
    WrenForeignClassMethods methods;
    methods.allocate = NULL;
    methods.finalize = NULL;
    if (strcmp(module, "main") == 0) {
        if (strcmp(className, "Window") == 0) {
            methods.allocate = C_windowAllocate;
        }
    }
    return methods;
}

WrenForeignMethodFn bindForeignMethod(
    WrenVM* vm,
    const char* module,
    const char* className,
    bool isStatic,
    const char* signature) {
    if (strcmp(module, "main") == 0) {
        if (strcmp(className, "Window") == 0) {
            if (!isStatic && strcmp(signature, "nodelay(_)") == 0)    return C_nodelay;
        } else if (strcmp(className, "Ncurses") == 0) {
            if (isStatic && strcmp(signature, "cbreak()") == 0)       return C_cbreak;
            if (isStatic && strcmp(signature, "noecho()") == 0)       return C_noecho;
            if (isStatic && strcmp(signature, "refresh()") == 0)      return C_refresh;
            if (isStatic && strcmp(signature, "getch()") == 0)        return C_getch;
            if (isStatic && strcmp(signature, "mvaddch(_,_,_)") == 0) return C_mvaddch;
            if (isStatic && strcmp(signature, "endwin()") == 0)       return C_endwin;
        } else if (strcmp(className, "C") == 0) {
            if (isStatic && strcmp(signature, "usleep(_)") == 0)      return C_usleep;
        }
    }
    return NULL;
}

static void writeFn(WrenVM* vm, const char* text) {
    printf("%s", text);
}

void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {
    switch (errorType) {
        case WREN_ERROR_COMPILE:
            printf("[%s line %d] [Error] %s\n", module, line, msg);
            break;
        case WREN_ERROR_STACK_TRACE:
            printf("[%s line %d] in %s\n", module, line, msg);
            break;
        case WREN_ERROR_RUNTIME:
            printf("[Runtime Error] %s\n", msg);
            break;
    }
}

char *readFile(const char *fileName) {
    FILE *f = fopen(fileName, "r");
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    rewind(f);
    char *script = malloc(fsize + 1);
    fread(script, 1, fsize, f);
    fclose(f);
    script[fsize] = 0;
    return script;
}

static void loadModuleComplete(WrenVM* vm, const char* module, WrenLoadModuleResult result) {
    if( result.source) free((void*)result.source);
}

WrenLoadModuleResult loadModule(WrenVM* vm, const char* name) {
    WrenLoadModuleResult result = {0};
    if (strcmp(name, "random") != 0 && strcmp(name, "meta") != 0) {
        result.onComplete = loadModuleComplete;
        char fullName[strlen(name) + 6];
        strcpy(fullName, name);
        strcat(fullName, ".wren");
        result.source = readFile(fullName);
    }
    return result;
}

int main(int argc, char **argv) {
    WrenConfiguration config;
    wrenInitConfiguration(&config);
    config.writeFn = &writeFn;
    config.errorFn = &errorFn;
    config.bindForeignClassFn = &bindForeignClass;
    config.bindForeignMethodFn = &bindForeignMethod;
    config.loadModuleFn = &loadModule;
    WrenVM* vm = wrenNewVM(&config);
    const char* module = "main";
    const char* fileName = "Snake.wren";
    char *script = readFile(fileName);
    WrenInterpretResult result = wrenInterpret(vm, module, script);
    switch (result) {
        case WREN_RESULT_COMPILE_ERROR:
            printf("Compile Error!\n");
            break;
        case WREN_RESULT_RUNTIME_ERROR:
            printf("Runtime Error!\n");
            break;
        case WREN_RESULT_SUCCESS:
            break;
    }
    wrenFreeVM(vm);
    free(script);
    return 0;
}

XPL0

XPL0, like implementations of BASIC on early personal computers, was designed to support video games like this. The 40x25 character color screen is a display mode built into the IBM-PC and simulated by XPL0 in the Raspberry Pi and Windows (EXPL) versions.

The initial length of the snake and the number of food items constantly available can be changed in the defined constants at the beginning. The speed is regulated by the duration of the Sound routine (intrinsic), which has a granularity of one DOS system tick, or about 1/18 second. The speed is currently set at nine steps per second.

def     Width=40, Height=25-1,          \playing area including border
        StartLength = 10,               \starting length of snake including head
        Morsels = 10;                   \number of food items constantly offered
int     Heading;                        \direction snake is heading
def     Up, Down, Left, Right;
int     Length,                         \current length of snake including head
        Score;                          \number of food items eaten
char    SnakeX(10000),                  \location of each segment including head
        SnakeY(10000),                  \ample room to grow
        FoodX(Morsels), FoodY(Morsels); \location of each food item
def     Black, Blue, Green, Cyan, Red, Magenta, Brown, White,  \attribute colors
        Gray, LBlue, LGreen, LCyan, LRed, LMagenta, Yellow, BWhite; \EGA palette

proc    PlaceFood(N);                   \Place Nth food item in playing area
int     N;
[FoodX(N):= Ran(Width-3) + 1;           \pick random location inside borders
 FoodY(N):= Ran(Height-3) + 1;
Cursor(FoodX(N), FoodY(N));             \show food
Attrib(Red<<4+LRed);
ChOut(6, ^@);
];      \PlaceFood


int     X, Y, I, C;
[SetVid($01);                           \set 40x25 text mode
ShowCursor(false);                      \turn off flashing cursor

Attrib(Blue<<4+LBlue);                  \show borders
Cursor(0, 0);
for X:= 0 to Width-1 do ChOut(6, ^+);
Cursor(0, Height-1);
for X:= 0 to Width-1 do ChOut(6, ^+);
for Y:= 0 to Height-1 do
    [Cursor(0, Y);  ChOut(6, ^+);
     Cursor(Width-1, Y);  ChOut(6, ^+);
    ];
Attrib(Black<<4+White);                 \show initial score
Cursor(0, 24);
Text(6, "Score: 0");
Score:= 0;

SnakeX(0):= Width/2;                    \start snake head at center
SnakeY(0):= Height/2;
Heading:= Left;
Length:= StartLength;
for I:= 1 to Length-1 do                \segments follow head to the right
    [SnakeX(I):= SnakeX(I-1) + 1;
     SnakeY(I):= SnakeY(I-1);
    ];
for I:= 0 to Morsels-1 do PlaceFood(I); \sow some tasty food

\Continuously move snake
loop \--------------------------------------------------------------------------
[Attrib(Black<<4+White);                \remove tail-end segment
Cursor(SnakeX(Length-1), SnakeY(Length-1));
ChOut(6, ^ );
Attrib(Green<<4+Yellow);                \add segment at head location
Cursor(SnakeX(0), SnakeY(0));
ChOut(6, ^#);

\Shift coordinates toward tail (+1 in case a segment gets added)
for I:= Length downto 1 do              \segment behind head gets head's coords
    [SnakeX(I):= SnakeX(I-1);
     SnakeY(I):= SnakeY(I-1);
    ];
if ChkKey then                          \key hit--get new movement direction
    [repeat C:= ChIn(1) until C # 0;    \remove arrow keys' prefix byte
    case C of
          $1B:  exit;                   \Escape, and scan codes for arrow keys
          $48:  if Heading # Down then Heading:= Up;
          $50:  if Heading # Up then Heading:= Down;
          $4B:  if Heading # Right then Heading:= Left;
          $4D:  if Heading # Left then Heading:= Right
    other       [];                     \ignore any other keystrokes
    ];
case Heading of                         \move head to its new location
  Up:   SnakeY(0):= SnakeY(0)-1;
  Down: SnakeY(0):= SnakeY(0)+1;
  Left: SnakeX(0):= SnakeX(0)-1;
  Right:SnakeX(0):= SnakeX(0)+1
other   [];
Cursor(SnakeX(0), SnakeY(0));           \show head at its new location
ChOut(6, ^8);

for I:= 0 to Morsels-1 do
    if SnakeX(0)=FoodX(I) & SnakeY(0)=FoodY(I) then
        [Score:= Score+1;               \ate a morsel
        Attrib(Black<<4+White);
        Cursor(7, 24);
        IntOut(6, Score*10);
        PlaceFood(I);                   \replenish morsel
        Length:= Length+1;              \grow snake one segment
        I:= Morsels;                    \over eating can be bad--quit for loop
        Sound(1, 1, 1500);              \BURP!
        Sound(1, 1, 1000);
        ];
if I = Morsels then Sound(0, 2, 1);     \no sound adds delay of 2/18th second

if SnakeX(0)=0 or SnakeX(0)=Width-1 or
   SnakeY(0)=0 or SnakeY(0)=Height-1 then
        quit;                           \snake hit border--game over
for I:= 1 to Length-1 do
    if SnakeX(0)=SnakeX(I) & SnakeY(0)=SnakeY(I) then
        quit;                           \snake bit itself--game over
]; \loop -----------------------------------------------------------------------
for I:= 1 to 8 do Sound(1, 1, 4000*I);  \death dirge
Sound(0, 36, 1);                        \pause 2 seconds to see result
OpenI(1);                               \flush any pending keystrokes
]