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

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)


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.


Amazing Hopper

Translation of: C

<lang Amazing Hopper> /* 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. */
  1. include <flow.h>
  2. include <flow-term.h>
  3. include <keys.h>
  1. enum 1,N,E,S,W
  2. enum 1,SPACE,FOOD,BORDER

DEF-MAIN(argv, argc)

  BREAK-ON
  STACK 16
  CLR-SCR
  MSET(C, quit, nHead, dir, Size, SCORE, counter, T,TPlay,ConsPrey )
  SET( symbol, " $@" )
  SET( w, 50 ) //50
  SET( h, 28 ) // 24
  SET( TIME, 100 )
  LET( Size :=  MUL(w,h) )
  SET( TLimit := 100 )
  VOID( big number, numL1, numL2, numL3 )
  GOSUB( set score )
  DIM( Size ) AS-ONES( board )
  HIDE-CURSOR
  GOSUB( put titles )
  GOSUB( start )
  GOSUB( show )
  GOSUB( ready ), SLEEP(3)
  TIC( T ), TIC( TPlay )
  WHILE( NOT (quit) ) 
     DEVIATE-IF( TLimit ~= TPlay ){
        GOSUB( show )
        WHEN( KEY-PRESSED? ){ 
           SCAN-CODE( C )
           SWITCH( 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 }
           SWEND
        }
        GOSUB( step )
     }
     DEVIATE-IF( 800 ~= T ) {
        TIME-=10, CLAMP(0,100,TIME)
        {TIME, 12, 52} GOSUB( put time )
     }
  WEND
  GOSUB(you lost),   SLEEP(1)
  GOSUB(game over ), SLEEP(2)
  LOCATE(ADD(h,1),1) PRNL
  SHOW-CURSOR
  PRNL

END

RUTINES

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

  SET( i,1 )
  [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( nHead := MUL( w, SUB( SUB( h, 1 ), MOD(h,2) )) DIV-INTO(2) )
  [ nHead ] {-5} CPUT( board )
  LET( dir := N )
  SRAND( ~SECONDS)
  GOSUB( plant )

RET

DEF-FUN( you lost )

  SET(i,1), SET(k,0), SET(n,1)
  LOCATE(1,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)
            PRNL("\033[38;15;3m\033[48;5;9m~\OFF")
        CEND
        ++n
     NEXT
  NEXT

RET

DEF-FUN( show )

  SET(i,1)
  MSET(j, k)
  SET(n,1)
  LOCATE(1,1)
  FOR( LE?(i, h), ++i)
     SET(j,1),
     FOR( LE?(j, w), ++j)
        LET( k := [ n ] GET(board) )
        COND( IS-NEG?( k ))
           PRN("\033[38;5;3m\033[48;5;15m~\OFF")
        ELS-COND( {k} IS-EQ?(BORDER))
              {"\033[38;5;4m\033[48;5;2m"}
              PRN( [k] GET(symbol),"\OFF")
        ELS-COND( {k}IS-EQ?(FOOD) )
              {"\033[38;5;15m\033[48;5;9m"}
              PRN( [k] GET(symbol),"\OFF")
        ELS
              {"\033[48;5;28m"}
              PRN( [k] GET(symbol),"\OFF")
        CEND
        ++n
     NEXT
     PRNL
  NEXT

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 )

  SET(len,0)
  LET( len := [nHead] GET(board) )
  SWITCH(dir)
     CASE (N){ nHead -= w, EXIT }
     CASE (S){ nHead += w, EXIT }
     CASE (W){ --nHead, EXIT }
     CASE (E){ ++nHead, EXIT }
  SWEND
  SWITCH( [nHead]CGET(board))
     CASE (SPACE){
         --len, LET( len := IF( IS-ZERO?(len), 1, len) )
         [nHead] { len }, CPUT(board) // keep in mind len is negative
         GOSUB( age )
         EXIT
     }
     CASE (FOOD){
         --len, LET( len := IF( IS-ZERO?(len), 1, len) )
         [nHead] { len }, CPUT(board)
         GOSUB( plant )
         ADD(SCORE,TIME), MOVE-TO(SCORE)
         {SCORE, 4, 52} GOSUB( put score )
         LET( TIME := 100 )
         ++counter, COND( GT?( counter, 5 ) )
                       LET( TLimit := SUB( TLimit,5 ))
                       CLAMP(30,100,TLimit)
                       LET( counter := 0 )
                    CEND
         ++ConsPrey
         COLOR-FG(10)
         LOCATE(20,52) PRNL("SPEED: ")
         LOC-ROW(21),  SET-ROUND(2)
                       PRNL( MUL(INV(TLimit),100), " M/S" )
                       SET-ROUND(-1)
         LOC-ROW(23)   PRNL("CONSUMED PREY:")
         LOC-ROW(24)   PRNL(ConsPrey,"\OFF")
         TIC( T ),{TIME, 12, 52} GOSUB( put time )
         EXIT
     }
     LET( quit := 1 )
     
  SWEND

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,52)   PRNL("\033[38;5;15mSCORE\OFF")
  {SCORE, 4, 52} GOSUB( put score )
  LOCATE(10,52)  PRNL("\033[38;5;11mTIME\OFF")
  {TIME, 12, 52} GOSUB( put time )
  COLOR-FG(15)
  LOCATE(26,52) SET-ITALIC, PRNL("  S P E E D")
  LOC-ROW(27)               PRNL("S N A K E!\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 ))
  
  {"\033[38;5;11m\ENF"}
  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) PRNL( STR-TO-UTF8(CHAR( [ PLUS-ONE(nB), x] CGET(big number) MUL-BY(219) )))
           ++x
        NEXT
      NEXT
      rx += 4
  NEXT
  PRNL("\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)
   {"\033[38;5;15m"}
   PERF-UP( i, ln, 1)
      LET( s := VAL( MID( 1, i, sp )) )
      [ PLUS-ONE(s) ]   // set interval to read element of arrays
      LOCATE( posx, posy )  
      PRNL ( STR-TO-UTF8( GET(numL1) ))
      LOCATE( PLUS-ONE(posx),posy )
      PRNL ( STR-TO-UTF8( GET(numL2) ))
      LOCATE( PLUS-TWO(posx),posy )
      PRNL ( STR-TO-UTF8( GET(numL3) ))
      posy += 2
   NEXT
   PRNL("\OFF")

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); PRNL( STR-TO-UTF8("  ▄   ▄▄  ▄  ▄▄  ▄ ▄ "))
  LOC-ROW(14); PRNL( STR-TO-UTF8(" █▄▀ █▀  █▄█ █ █ ▀▄▀ "))
  LOC-ROW(15); PRNL( STR-TO-UTF8(" ▀ ▀  ▀▀ ▀ ▀ ▀▄▀  ▀  "))
  PRNL("\OFF")

RET

DEF-FUN( game over )

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

RET </lang>

Output:

Init game:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@                                                @ SCORE
@                                                @
@                                                @ ┌┐                  
@                                                @ ││                  
@                                                @ └┘                  
@                                                @                     
@                                                @
@                                                @
@                                                @ TIME
@                                                @
@                                                @ ██  ███ ███         
@                ▄   ▄▄  ▄  ▄▄  ▄ ▄              @  █  █ █ █ █         
@               █▄▀ █▀  █▄█ █ █ ▀▄▀              @  █  █ █ █ █         
@               ▀ ▀  ▀▀ ▀ ▀ ▀▄▀  ▀               @  █  █ █ █ █         
@                                                @ ███ ███ ███         
@                                                @
@                                    $           @
@                                                @
@                                                @
@                                                @
@                                                @
@                                                @
@                                                @
@                                                @
@                                                @   S P E E D
@                                                @ S N A K E!
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Play game:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@                                                @ SCORE
@                                                @
@                                                @ ┌┐┌┐┌┐              
@                                                @ ├┐┌┘││              
@                                                @ └┘└┘└┘              
@                                                @                     
@                                                @
@                                                @
@                                                @ TIME
@                                                @
@                                                @ ███ ███             
@                                                @   █ █ █             
@                                                @   █ █ █             
@                                                @   █ █ █             
@                                                @   █ ███             
@                                                @
@                                                @
@                                                @
@  ~~~~~~                                        @ SPEED:
@       ~                                        @ 1.00 M/S
@       ~                                        @ 
@       ~~~~~~   $                               @ CONSUMED PREY:
@                                                @ 11
@                                                @
@                                                @   S P E E D
@                                                @ S N A K E!
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Game over:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@                                                @ SCORE
@                                                @
@                                                @  ┐┌┐┌┐┌┐            
@                                                @  │└┐└┐││            
@                                                @  ┴└┘└┘└┘            
@                                                @                     
@                                                @
@                                                @
@                                                @ TIME
@                                                @
@                 ▄▄  ▄   ▄ ▄   ▄▄               @ █ █ ███             
@                █ ▄ █▄█ █ █ █ █▀                @ █ █ █ █             
@                 ▀▀ ▀ ▀ ▀ ▀ ▀  ▀▀               @ ███ █ █             
@                  ▄  ▄ ▄  ▄▄  ▄                 @   █ █ █             
@                 █ █ █ █ █▀  █▄▀                @   █ ███             
@ ~                ▀   ▀   ▀▀ ▀ ▀                @
@ ~                                              @
@ ~                                              @
@ ~                                              @ SPEED:
@ ~                                              @ 1.10 M/S
@ ~~~~~~~~~~~~~                                  @
@             ~                                  @ CONSUMED PREY:
@             ~                                  @ 17
@             ~                                  @
@             ~                                  @   S P E E D
@             ~              $                   @ S N A K E!
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

AutoHotkey

<lang 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

-----------------------------------------
  1. 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

  1. 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] }

-----------------------------------------</lang>

BASIC

FreeBASIC

<lang freebasic> REM Snake

  1. 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 '-------------------------- </lang>

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). <lang locobasic>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</lang>

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. <lang zxbasic> 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</lang>

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. <lang c>// 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.

  1. ifdef __linux__
  2. define _POSIX_C_SOURCE 200809L
  3. include <time.h> // nanosleep
  4. 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(); }

  1. endif

// An implementation for some other system...

  1. ifdef _WIN32
  2. error "not implemented"
  3. endif

// The game logic, system-independent

  1. include <time.h> // time
  2. include <stdlib.h> // rand, srand
  1. define w 80
  2. 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; }</lang>

C++

Simple Windows console implementation.

 

<lang cpp>

  1. include <windows.h>
  2. include <ctime>
  3. include <iostream>
  4. 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;

} </lang>

Delphi

Library: Vcl.Forms
Library: Vcl.Dialogs
Translation of: JavaScript

<lang Delphi> 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.</lang> Form resources: <lang Delphi> object SnakeApp: TSnakeApp

 OnCreate = FormCreate

end </lang>

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). <lang Go>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} )</lang>

Haskell

<lang 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)</lang>

Extra credit

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

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

and add local definition:

<lang haskell> 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</lang>

Java

See Snake/Java.

JavaScript

You need the P5 Library to run this code! <lang javascript> 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 );

} </lang>

Julia

Makie version in 99 lines. <lang julia>using Makie

mutable struct SnakeGame

   height
   width
   snake
   food

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 = Node(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])), textsize = 50, align = (:center, :top))
   direction = Ref{Any}(nothing)
   on(scene.events.keyboardbuttons) do but
       if ispressed(but, Keyboard.left)
           direction[] = CartesianIndex(-1,0)
       elseif ispressed(but, Keyboard.up)
           direction[] = CartesianIndex(0,1)
       elseif ispressed(but, Keyboard.down)
           direction[] = CartesianIndex(0,-1)
       elseif ispressed(but, Keyboard.right)
           direction[] = CartesianIndex(1,0)
       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() </lang>

Kotlin

Translation of: C++
Works with: Windows 10

<lang scala>// 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()

}</lang>

Output:
Similar to C++ entry

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”. <lang Nim>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()</lang>

OCaml

Library: OCamlSDL2

<lang ocaml>(* 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</lang>

Perl

 

<lang 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  }

}</lang>

Phix

Translation of: C++

<lang Phix>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()</lang>

Python

Using Pygame. Works with Python >= 3.7. <lang python>from __future__ import annotations

import itertools import random

from enum import Enum

from typing import Any from typing import Tuple

import pygame as pg

from pygame import Color from pygame import Rect

from pygame.surface import Surface

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


class Direction(Enum):

   UP = (0, -1)
   DOWN = (0, 1)
   LEFT = (-1, 0)
   RIGHT = (1, 0)
   def opposite(self, other: Direction):
       return (self[0] + other[0], self[1] + other[1]) == (0, 0)
   def __getitem__(self, i: int):
       return self.value[i]


class SnakeHead(Sprite):

   def __init__(
       self,
       size: int,
       position: Tuple[int, int],
       facing: Direction,
       bounds: Rect,
   ) -> None:
       super().__init__()
       self.image = Surface((size, size))
       self.image.fill(Color("aquamarine4"))
       self.rect = self.image.get_rect()
       self.rect.center = position
       self.facing = facing
       self.size = size
       self.speed = size
       self.bounds = bounds
   def update(self, *args: Any, **kwargs: Any) -> None:
       # 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)

</lang>

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.

<lang perl6>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();</lang>

Rust

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

Used winsafe - a safe rust bindings library for Win32 GUI: young but very handy, with links to docs.microsoft.com from doc and src for all Win32 entities involved.

Along the way, the possibility of restarting while maintaining the length of the snake has been implemented. Now a long snake is available to everyone!

snake game screenshot <lang fsharp>/* add to file Cargo.toml: [dependencies] winsafe = "0.0.8" rand = "0.8.4"

  • /
  1. ![windows_subsystem = "windows"]

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

const STEP: i32 = 3; // px, motion per frame. STEP and FPS determine the smoothness and speed of the animation. const FPS: u32 = 90; const CELL: i32 = 21; // px, game grid (logical step). Will be aligned by STEP const FIELD_W: i32 = 20; // width of the square field in CELLs const SNAKE_W: i32 = 20; // px const ROUNDING: SIZE = SIZE::new(SNAKE_W / 2, SNAKE_W / 2);

const RATIO: i32 = CELL / STEP; const START_CELL: i32 = FIELD_W / 2 * RATIO; /// total field width (with overlap for collisions) in STEPs const TW: i32 = (FIELD_W + 2) * RATIO;

  1. [derive(Clone, Copy)]
  2. [repr(i32)]

enum Direction {

   Start = 0,
   A = -1,
   D = 1,
   W = -TW,
   S = TW,

} use Direction::*;

struct Context {

   wnd: gui::WindowMain,
   snake: Vec<i32>, // [ids_rect] where id_rect = y * TW + x (where x, y: nSTEPs)
   id_r: [i32; 6],  // ID 6 rectangles to color in next frame (bg, tail, turn, body, food, head)
   gap: i32,        // gap in STEPs between animation and logic cell (negative - remove tail)
   dir: Direction,
   ordered_dir: Direction,

} impl Context {

   fn new(wnd: gui::WindowMain, len: usize) -> Self {
       Self {
           wnd,
           snake: vec![START_CELL; len.saturating_sub(RATIO as usize)],
           id_r: [START_CELL; 6],
           gap: 0,
           dir: Start,
           ordered_dir: S,
       }
   }

}

pub fn main() {

   let [bg, tail, turn, body, food, head] = [0usize, 1, 2, 3, 4, 5];
   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: winsafe::SIZE::new(FIELD_W * RATIO * STEP, FIELD_W * RATIO * STEP),
       ex_style: co::WS_EX::CLIENTEDGE,
       class_bg_brush: brushes[bg],
       ..Default::default()
   });
   let context = Rc::new(RefCell::new(Context::new(wnd.clone(), 0)));
   wnd.on().wm_key_down({
       let context = Rc::clone(&context);
       move |k| {
           let mut ctx = context.borrow_mut();
           match (ctx.dir, k.char_code as u8) {
               (Start, bt @ (b' ' | 113)) => {
                   let len = ctx.snake.len(); //                              113 == F2 key
                   *ctx = Context::new(ctx.wnd.clone(), if bt == b' ' { len } else { 0 });
                   ctx.wnd.hwnd().InvalidateRect(None, true)?; // call .wm_paint() with erase
                   ctx.wnd.hwnd().SetTimer(1, 1000 / FPS, None)?;
               }
               (W | S, bt @ (b'A' | b'D')) => ctx.ordered_dir = if bt == b'A' { A } else { D },
               (A | D, bt @ (b'S' | b'W')) => ctx.ordered_dir = if bt == b'S' { S } else { W },
               _ => (),
           }
           Ok(())
       }
   });
   wnd.on().wm_timer(1, {
       let context = Rc::clone(&context);
       let cells: Vec<i32> = (1..=FIELD_W)
           .flat_map(|y| (1..=FIELD_W).map(move |x| (y * TW + x) * RATIO))
           .collect();
       move || {
           let mut ctx = context.borrow_mut();
           let new_h = ctx.id_r[head] + ctx.dir as i32;
           ctx.id_r[body] = ctx.id_r[head];
           ctx.id_r[head] = new_h;
           if ctx.gap < 0 {
               ctx.id_r[bg] = ctx.snake.remove(0);
               ctx.id_r[tail] = ctx.snake[0];
               ctx.id_r[turn] = ctx.snake[RATIO as usize / 2];
           }
           ctx.gap -= ctx.gap.signum();
           if ctx.gap == 0 {
               ctx.dir = ctx.ordered_dir;
               let hw = ctx.wnd.hwnd();
               let eat = new_h == ctx.id_r[food];
               if !eat && (cells.binary_search(&new_h).is_err() || ctx.snake.contains(&&new_h)) {
                   hw.KillTimer(1)?;
                   hw.SetWindowText(&(hw.GetWindowText()? + "  Restart: F2 (with save - Space)"))?;
                   ctx.dir = Start;
                   return Ok(());
               } else if eat || ctx.id_r[food] == 0 && ctx.id_r[tail] != START_CELL {
                   let mut snk_cells: Vec<_> = ctx.snake.iter().step_by(RATIO as usize).collect();
                   if eat && snk_cells.len() == cells.len() - 2 {
                       hw.SetWindowText(&format!("Snake - EATEN ALL: {} !!!", snk_cells.len()))?
                   } else if eat {
                       hw.SetWindowText(&format!("Snake - Eaten: {}.", snk_cells.len()))?
                   }
                   if ctx.id_r[tail] == START_CELL || eat && snk_cells.len() == cells.len() - 2 {
                       ctx.id_r[food] = 0; // hide food if not all of the saved snake has come out or everything is eaten
                   } else if snk_cells.len() + 1 < cells.len() {
                       snk_cells.sort();
                       ctx.id_r[food] = *(cells.iter())
                           .filter(|i| **i != new_h && snk_cells.binary_search(i).is_err())
                           .nth(rand::thread_rng().gen_range(0..cells.len() - snk_cells.len() - 1))
                           .unwrap();
                   }
               }
               ctx.gap = if eat { RATIO } else { -RATIO }
           }
           ctx.snake.push(new_h);
           ctx.wnd.hwnd().InvalidateRect(None, false)?; // call .wm_paint() without erase
           Ok(())
       }
   });
   wnd.on().wm_paint(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_rect, &brush) in ctx.id_r.iter().zip(&brushes) {
           hdc.SelectObjectBrush(brush)?;
           let left = id_rect % TW * STEP - (STEP * RATIO + SNAKE_W) / 2;
           let top = id_rect / TW * STEP - (STEP * RATIO + SNAKE_W) / 2;
           hdc.RoundRect(
               winsafe::RECT {
                   left,
                   top,
                   right: left + SNAKE_W,
                   bottom: top + SNAKE_W,
               },
               ROUNDING,
           )?;
       }
       ctx.wnd.hwnd().EndPaint(&ps);
       Ok(())
   });
   if let Err(e) = wnd.run_main(None) {
       winsafe::HWND::NULL
           .MessageBox(&e.to_string(), "Uncaught error", co::MB::ICONERROR)
           .unwrap();
   }

}</lang>

Sidef

<lang ruby>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</lang>

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. <lang ecmascript>/* 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()</lang>
Now embed this script in the following C program, compile and run it. <lang c>/* gcc snake.c -o snake -lncurses -lwren -lm */

  1. include <stdio.h>
  2. include <stdlib.h>
  3. include <string.h>
  4. include <ncurses.h>
  5. include <unistd.h>
  6. 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;

}</lang>

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.

<lang XPL0>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 ]</lang>