I'm working on modernizing Rosetta Code's infrastructure. Starting with communications. Please accept this time-limited open invite to RC's Slack.. --Michael Mol (talk) 20:59, 30 May 2020 (UTC)

Snake

From Rosetta Code
Snake is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
This page uses content from Wikipedia. The original article was at Snake_(video_game). The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance)



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


Task

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

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

The game ends when the snake attempts to eat himself.

AutoHotkey[edit]

gosub Init
Gui, +AlwaysOnTop
Gui, font, s12, consolas
Gui, add, Edit, vEditGrid x10 y10 ReadOnly, % grid2Text(oGrid)
Gui, add, Text, vScore x10 y+10 w200 ReadOnly, % "Your Score = " Score
Gui, show,, Snake
GuiControl, Focus, Score
return
;-----------------------------------------
Init:
Width := 100, Height := 30 ; set grid size
tail := 20, step := 10
Timer := 75
item := 0, direction := ""
oTrail := [], Score := 0
xx := generateGrid(width, Height)
oGrid := xx.1
row := xx.2, col := xx.3
return
;-----------------------------------------
Move:
if !item
{
loop
{
Random, itemC, 3, % width-2
Random, itemR, 3, % Height-2
}
until, !oGrid[itemR, itemC]
oGrid[itemR, itemC] := "@"
item := true
}
gosub, crawl
return
;-----------------------------------------
#IfWinActive, Snake
left::
Right::
up::
Down::
if ((A_ThisHotkey = "right") && (Direction = "left"))
|| ((A_ThisHotkey = "left") && (Direction = "right"))
|| ((A_ThisHotkey = "up") && (Direction = "Down"))
|| ((A_ThisHotkey = "Down") && (Direction = "up"))
|| ((A_ThisHotkey = "right") && (Direction = "right"))
|| ((A_ThisHotkey = "left") && (Direction = "left"))
|| ((A_ThisHotkey = "up") && (Direction = "up"))
|| ((A_ThisHotkey = "Down") && (Direction = "Down"))
return
 
Direction := A_ThisHotkey
gosub, crawl
return
#IfWinActive
;-----------------------------------------
crawl:
switch, Direction
{
case "left" : oGrid[row , col--] := 1
case "Right" : oGrid[row , col++] := 1
case "up" : oGrid[row--, col ] := 1
case "Down" : oGrid[row++, col ] := 1
}
 
; out of bounds or snake eats itself
if oGrid[row, col] = 1 || col < 1 || col > width || row < 1 || row > height
gosub, YouLose
 
; snake eats item
if (oGrid[row, col] = "@")
{
item := false
tail += step
GuiControl,, Score, % "Your Score = " ++Score
}
 
; snake advances
oGrid[row, col] := 2
oTrail.Push(row "," col)
if (oTrail.count() >= tail)
x := StrSplit(oTrail.RemoveAt(1), ","), oGrid[x.1, x.2] := false
 
GuiControl,, EditGrid, % grid2Text(oGrid)
SetTimer, Move, % 0-Timer
return
;-----------------------------------------
YouLose:
SetTimer, Move, Off
MsgBox, 262180, ,% "Your Score is " Score "`nPlay Again?"
IfMsgBox, No
ExitApp
 
gosub Init
GuiControl,, EditGrid, % grid2Text(oGrid)
return
;-----------------------------------------
grid2Text(oGrid){
for row, obj in oGrid {
for col, val in obj ; @=item, 2=Head, 1=tail
text .= val = "@" ? "@" : val =2 ? "█" : val = 1 ? "▓" : " "
text .= "`n"
}
return trim(text, "`n")
}
;-----------------------------------------
generateGrid(width, Height){
global oTrail
oGrid := []
loop, % width
{
col := A_Index
loop, % Height
row := A_Index, oGrid[row, col] := false
}
Random, col, 3, % width-2
Random, row, 3, % Height-2
oGrid[row, col] := 2
oTrail.Push(row "," col)
return [oGrid, row, col]
}
;-----------------------------------------

BASIC[edit]

FreeBASIC[edit]

 
REM Snake
 
#define EXTCHAR Chr(255)
 
Dim Shared As Integer puntos, contar, longitud, posX, posY, oldhi = 0
Dim Shared As Integer x(500), y(500)
For contar = 1 To 500
x(contar) = 0 : y(contar) = 0
Next contar
Dim Shared As Byte fruitX, fruitY, convida
Dim Shared As Single delay
Dim Shared As String direccion, usuario
 
Sub Intro
Cls
Color 15, 0
Print " _ _ _ _ "
Print " / \ / \ / \ / \ "
Print " ___________/ _\/ _\/ _\/ _\____________ "
Print " __________/ /_/ /_/ /_/ /______________ "
Print " | /\ /\ /\ / \ \___ "
Print " |/ \_/ \_/ \_/ \ "+Chr(248)+"\ "
Print " \___/--< "
Color 14, 0
Locate 10, 28: Print "---SNAKE---"
Locate 12, 4: Print "Para jugar, usa las teclas de flecha para moverte."
Locate 13, 4: Print "Pulsa <1-3> para velocidad, o <Esc> para salir."
If puntos > oldhi Then oldhi = puntos
Locate 14, 4: Print "M xima puntuaci¢n: "; oldhi
usuario = ""
While usuario = ""
usuario = Inkey
Wend
If usuario = Chr(27) Then End 'ESC
delay = .14
If usuario = "1" Then delay = .26
If usuario = "3" Then delay = .08
Cls
longitud = 9
puntos = 0
posX = 40 : posY = 10
direccion = "derecha"
convida = true
fruitX = Int(Rnd * 79) + 1
fruitY = Int(Rnd * 20) + 1
Locate 22, 1
For contar = 1 To 80
Print Chr(196); ''"-";
Next contar
End Sub
 
Sub MenuPrincipal
Dim As Integer num, oldX, oldY
Dim As Single tm, tm2
 
Color 10
Locate 23, 2: Print "SNAKE - <Esc> salir - Puntos: "; puntos
If posX = fruitX And posY = fruitY Then
longitud += 1
puntos += 1
fruitX = Int(Rnd * 79) + 1
fruitY = Int(Rnd * 21) + 1
End If
Locate fruitY, fruitX : Color 12: Print Chr(01) : Color 10'"@"
x(0) = posX : y(0) = posY
 
For contar = 1 To 499
num = 500 - contar
x(num) = x(num - 1)
y(num) = y(num - 1)
Next contar
 
oldX = x(longitud) : oldY = y(longitud)
If oldX > 0 And oldY > 0 Then Locate oldY, oldX : Print " "
 
Locate posY, posX: Print Chr(219) '"Û"
tm = Timer
tm2 = tm + delay
While tm < tm2
tm = Timer
usuario = Inkey
If usuario = EXTCHAR & "H" Then direccion = "arriba"
If usuario = EXTCHAR & "P" Then direccion = "abajo"
If usuario = EXTCHAR & "K" Then direccion = "izquierda"
If usuario = EXTCHAR & "M" Then direccion = "derecha"
If usuario = Chr(27) Then Intro
Wend
If direccion = "derecha" Then posX += 1
If posX > 80 Then convida = false
If direccion = "izquierda" Then posX -= 1
If posX < 1 Then convida = false
If direccion = "arriba" Then posY -= 1
If posY < 1 Then convida = false
If direccion = "abajo" Then posY += 1
If posY > 21 Then convida = false
 
For contar = 0 To longitud
If posX = x(contar) And posY = y(contar) Then convida = false
Next contar
 
If convida = false Then
Cls : Locate 11, 19: Print "Pulsa <space>..."
Locate 10, 18: Print "Has muerto! Con"; puntos; " puntos." : Sleep
While Inkey = "": Wend
Intro
End If
MenuPrincipal
End Sub
 
'--- Programa Principal ---
Randomize Timer
Intro
MenuPrincipal
End
'--------------------------
 

Locomotive Basic[edit]

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

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

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

ZX Spectrum Basic[edit]

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

A screenshot is here.

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

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

C[edit]

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

// Snake
 
// The problem with implementing this task in C is, the language standard
// does not cover some details essential for interactive games:
// a nonblocking keyboard input, a positional console output,
// a and millisecond-precision timer: these things are all system-dependent.
 
// Therefore the program is split in two pieces, a system-independent
// game logic, and a system-dependent UI, separated by a tiny API:
char nonblocking_getch();
void positional_putch(int x, int y, char ch);
void millisecond_sleep(int n);
void init_screen();
void update_screen();
void close_screen();
 
// The implementation of a system-dependent part.
// Requires POSIX IEEE 1003.1-2008 compliant system and ncurses library.
#ifdef __linux__
#define _POSIX_C_SOURCE 200809L
#include <time.h> // nanosleep
#include <ncurses.h> // getch, mvaddch, and others
char nonblocking_getch() { return getch(); }
void positional_putch(int x, int y, char ch) { mvaddch(x, y, ch); }
void millisecond_sleep(int n) {
struct timespec t = { 0, n * 1000000 };
nanosleep(&t, 0);
// for older POSIX standards, consider usleep()
}
void update_screen() { refresh(); }
void init_screen() {
initscr();
noecho();
cbreak();
nodelay(stdscr, TRUE);
}
void close_screen() { endwin(); }
#endif
 
// An implementation for some other system...
#ifdef _WIN32
#error "not implemented"
#endif
 
// The game logic, system-independent
#include <time.h> // time
#include <stdlib.h> // rand, srand
 
#define w 80
#define h 40
 
int board[w * h];
int head;
enum Dir { N, E, S, W } dir;
int quit;
 
enum State { SPACE=0, FOOD=1, BORDER=2 };
// negative values denote the snake (a negated time-to-live in given cell)
 
// reduce a time-to-live, effectively erasing the tail
void age() {
int i;
for(i = 0; i < w * h; ++i)
if(board[i] < 0)
++board[i];
}
 
// put a piece of food at random empty position
void plant() {
int r;
do
r = rand() % (w * h);
while(board[r] != SPACE);
board[r] = FOOD;
}
 
// initialize the board, plant a very first food item
void start(void) {
int i;
for(i = 0; i < w; ++i)
board[i] = board[i + (h - 1) * w] = BORDER;
for(i = 0; i < h; ++i)
board[i * w] = board[i * w + w - 1] = BORDER;
head = w * (h - 1 - h % 2) / 2; // screen center for any h
board[head] = -5;
dir = N;
quit = 0;
srand(time(0));
plant();
}
 
void step() {
int len = board[head];
switch(dir) {
case N: head -= w; break;
case S: head += w; break;
case W: --head; break;
case E: ++head; break;
}
switch(board[head]) {
case SPACE:
board[head] = len - 1; // keep in mind len is negative
age();
break;
case FOOD:
board[head] = len - 1;
plant();
break;
default:
quit = 1;
}
}
 
void show() {
const char * symbol = " @.";
int i;
for(i = 0; i < w * h; ++i)
positional_putch(i / w, i % w,
board[i] < 0 ? '#' : symbol[board[i]]);
update_screen();
}
 
int main (int argc, char * argv[]) {
init_screen();
start();
do {
show();
switch(nonblocking_getch()) {
case 'i': dir = N; break;
case 'j': dir = W; break;
case 'k': dir = S; break;
case 'l': dir = E; break;
case 'q': quit = 1; break;
}
step();
millisecond_sleep(100); // beware, this approach
// is not suitable for anything but toy projects like this
}
while(!quit);
millisecond_sleep(999);
close_screen();
return 0;
}

C++[edit]

Simple Windows console implementation.

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

Delphi[edit]

Library: Vcl.Forms
Library: Vcl.Dialogs
Translation of: JavaScript
 
unit SnakeGame;
 
interface
 
uses
Winapi.Windows, System.SysUtils,
System.Classes, Vcl.Graphics, Vcl.Forms, Vcl.Dialogs,
System.Generics.Collections, Vcl.ExtCtrls;
 
type
TSnakeApp = class(TForm)
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure FormPaint(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure DoFrameStep(Sender: TObject);
procedure Reset;
private
{ Private declarations }
FrameTimer: TTimer;
public
{ Public declarations }
end;
 
TSnake = class
len: Integer;
alive: Boolean;
pos: TPoint;
posArray: TList<TPoint>;
dir: Byte;
private
function Eat(Fruit: TPoint): Boolean;
function Overlap: Boolean;
procedure update;
public
procedure Paint(Canvas: TCanvas);
procedure Reset;
constructor Create;
destructor Destroy; override;
end;
 
TFruit = class
FruitTime: Boolean;
pos: TPoint;
constructor Create;
procedure Reset;
procedure Paint(Canvas: TCanvas);
private
procedure SetFruit;
end;
 
const
L = 1;
R = 2;
D = 4;
U = 8;
 
var
SnakeApp: TSnakeApp;
block: Integer = 24;
wid: Integer = 30;
hei: Integer = 20;
fruit: TFruit;
snake: TSnake;
 
implementation
 
{$R *.dfm}
 
function Rect(x, y, w, h: Integer): TRect;
begin
Result := TRect.Create(x, y, x + w, y + h);
end;
 
{ TSnake }
 
constructor TSnake.Create;
begin
posArray := TList<TPoint>.Create;
Reset;
end;
 
procedure TSnake.Paint(Canvas: TCanvas);
var
pos: TPoint;
i, l: Integer;
r: TRect;
begin
with Canvas do
begin
Brush.Color := rgb(130, 190, 0);
 
i := posArray.count - 1;
l := posArray.count;
while True do
begin
pos := posArray[i];
dec(i);
r := rect(pos.x * block, pos.y * block, block, block);
FillRect(r);
dec(l);
if l = 0 then
Break;
end;
end;
end;
 
procedure TSnake.Reset;
begin
alive := true;
pos := Tpoint.Create(1, 1);
posArray.Clear;
posArray.Add(Tpoint.Create(pos));
len := posArray.Count;
dir := r;
end;
 
destructor TSnake.Destroy;
begin
posArray.Free;
inherited;
end;
 
function TSnake.Eat(Fruit: TPoint): Boolean;
begin
result := (pos.X = Fruit.X) and (pos.y = Fruit.y);
if result then
begin
inc(len);
if len > 5000 then
len := 500;
end;
end;
 
function TSnake.Overlap: Boolean;
var
aLen: Integer;
tp: TPoint;
i: Integer;
begin
aLen := posArray.count - 1;
 
for i := 0 to aLen - 1 do
begin
tp := posArray[i];
if (tp.x = pos.x) and (tp.y = pos.y) then
Exit(True);
end;
Result := false;
end;
 
procedure TSnake.update;
begin
if not alive then
exit;
case dir of
l:
begin
dec(pos.X);
if pos.X < 1 then
pos.x := wid - 2
end;
r:
begin
inc(pos.x);
if (pos.x > (wid - 2)) then
pos.x := 1;
end;
U:
begin
dec(pos.y);
 
if (pos.y < 1) then
pos.y := hei - 2
end;
D:
begin
inc(pos.y);
 
if (pos.y > hei - 2) then
pos.y := 1;
end;
end;
if Overlap then
alive := False
else
begin
posArray.Add(TPoint(pos));
 
if len < posArray.Count then
posArray.Delete(0);
end;
end;
 
{ TFruit }
 
constructor TFruit.Create;
begin
Reset;
end;
 
procedure TFruit.Paint(Canvas: TCanvas);
var
r: TRect;
begin
with Canvas do
begin
Brush.Color := rgb(200, 50, 20);
 
r := Rect(pos.x * block, pos.y * block, block, block);
 
FillRect(r);
end;
end;
 
procedure TFruit.Reset;
begin
fruitTime := true;
pos := Tpoint.Create(0, 0);
end;
 
procedure TFruit.SetFruit;
begin
pos.x := Trunc(Random(wid - 2) + 1);
pos.y := Trunc(Random(hei - 2) + 1);
fruitTime := false;
end;
 
procedure TSnakeApp.DoFrameStep(Sender: TObject);
begin
Invalidate;
end;
 
procedure TSnakeApp.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FrameTimer.Free;
snake.Free;
Fruit.Free;
end;
 
procedure TSnakeApp.FormCreate(Sender: TObject);
begin
Canvas.pen.Style := psClear;
ClientHeight := block * hei;
ClientWidth := block * wid;
DoubleBuffered := True;
KeyPreview := True;
 
OnClose := FormClose;
OnKeyDown := FormKeyDown;
OnPaint := FormPaint;
 
snake := TSnake.Create;
Fruit := TFruit.Create();
FrameTimer := TTimer.Create(nil);
FrameTimer.Interval := 250;
FrameTimer.OnTimer := DoFrameStep;
FrameTimer.Enabled := True;
end;
 
procedure TSnakeApp.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
 
function ValidDir(value: Byte): Byte;
var
combination: Byte;
begin
combination := (value or snake.dir);
if (combination = 3) or (combination = 12) then
Result := snake.dir
else
Result := value;
end;
 
begin
case Key of
VK_LEFT:
snake.dir := ValidDir(l);
VK_RIGHT:
snake.dir := ValidDir(r);
VK_UP:
snake.dir := ValidDir(U);
VK_DOWN:
snake.dir := ValidDir(D);
VK_ESCAPE:
Reset;
end;
end;
 
procedure TSnakeApp.FormPaint(Sender: TObject);
var
i: Integer;
r: TRect;
frameR: Double;
begin
with Canvas do
begin
Brush.Color := rgb(0, $22, 0);
FillRect(ClipRect);
Brush.Color := rgb(20, 50, 120);
 
for i := 0 to wid - 1 do
begin
r := rect(i * block, 0, block, block);
FillRect(r);
r := rect(i * block, ClientHeight - block, block, block);
FillRect(r);
end;
 
for i := 1 to hei - 2 do
begin
r := Rect(1, i * block, block, block);
FillRect(r);
r := Rect(ClientWidth - block, i * block, block, block);
FillRect(r);
end;
 
if (Fruit.fruitTime) then
begin
Fruit.setFruit();
frameR := FrameTimer.Interval * 0.95;
if frameR < 30 then
frameR := 30;
FrameTimer.Interval := trunc(frameR);
end;
 
Fruit.Paint(Canvas);
snake.update();
 
if not snake.alive then
begin
FrameTimer.Enabled := False;
Application.ProcessMessages;
ShowMessage('Game over');
Reset;
exit;
end;
 
if (snake.eat(Fruit.pos)) then
Fruit.fruitTime := true;
snake.Paint(Canvas);
 
Brush.Style := bsClear;
Font.Color := rgb(200, 200, 200);
Font.Size := 18;
TextOut(50, 0, (snake.len - 1).ToString);
end;
end;
 
procedure TSnakeApp.Reset;
begin
snake.Reset;
Fruit.Reset;
FrameTimer.Interval := 250;
FrameTimer.Enabled := True;
end;
end.

Form resources:

 
object SnakeApp: TSnakeApp
OnCreate = FormCreate
end
 

Go[edit]

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

package main
 
import (
"errors"
"fmt"
"log"
"math/rand"
"time"
 
termbox "github.com/nsf/termbox-go"
)
 
func main() {
rand.Seed(time.Now().UnixNano())
score, err := playSnake()
if err != nil {
log.Fatal(err)
}
fmt.Println("Final score:", score)
}
 
type snake struct {
body []position // tail to head positions of the snake
heading direction
width, height int
cells []termbox.Cell
}
 
type position struct {
X int
Y int
}
 
type direction int
 
const (
North direction = iota
East
South
West
)
 
func (p position) next(d direction) position {
switch d {
case North:
p.Y--
case East:
p.X++
case South:
p.Y++
case West:
p.X--
}
return p
}
 
func playSnake() (int, error) {
err := termbox.Init()
if err != nil {
return 0, err
}
defer termbox.Close()
 
termbox.Clear(fg, bg)
termbox.HideCursor()
s := &snake{
// It would be more efficient to use a circular
// buffer instead of a plain slice for s.body.
body: make([]position, 0, 32),
cells: termbox.CellBuffer(),
}
s.width, s.height = termbox.Size()
s.drawBorder()
s.startSnake()
s.placeFood()
s.flush()
 
moveCh, errCh := s.startEventLoop()
const delay = 125 * time.Millisecond
for t := time.NewTimer(delay); ; t.Reset(delay) {
var move direction
select {
case err = <-errCh:
return len(s.body), err
case move = <-moveCh:
if !t.Stop() {
<-t.C // handles race between moveCh and t.C
}
case <-t.C:
move = s.heading
}
if s.doMove(move) {
time.Sleep(1 * time.Second)
break
}
}
 
return len(s.body), err
}
 
func (s *snake) startEventLoop() (<-chan direction, <-chan error) {
moveCh := make(chan direction)
errCh := make(chan error, 1)
go func() {
defer close(errCh)
for {
switch ev := termbox.PollEvent(); ev.Type {
case termbox.EventKey:
switch ev.Ch { // WSAD and HJKL movement
case 'w', 'W', 'k', 'K':
moveCh <- North
case 'a', 'A', 'h', 'H':
moveCh <- West
case 's', 'S', 'j', 'J':
moveCh <- South
case 'd', 'D', 'l', 'L':
moveCh <- East
case 0:
switch ev.Key { // Cursor key movement
case termbox.KeyArrowUp:
moveCh <- North
case termbox.KeyArrowDown:
moveCh <- South
case termbox.KeyArrowLeft:
moveCh <- West
case termbox.KeyArrowRight:
moveCh <- East
case termbox.KeyEsc: // Quit
return
}
}
case termbox.EventResize:
// TODO
errCh <- errors.New("terminal resizing unsupported")
return
case termbox.EventError:
errCh <- ev.Err
return
case termbox.EventInterrupt:
return
}
}
}()
return moveCh, errCh
}
 
func (s *snake) flush() {
termbox.Flush()
s.cells = termbox.CellBuffer()
}
 
func (s *snake) getCellRune(p position) rune {
i := p.Y*s.width + p.X
return s.cells[i].Ch
}
func (s *snake) setCell(p position, c termbox.Cell) {
i := p.Y*s.width + p.X
s.cells[i] = c
}
 
func (s *snake) drawBorder() {
for x := 0; x < s.width; x++ {
s.setCell(position{x, 0}, border)
s.setCell(position{x, s.height - 1}, border)
}
for y := 0; y < s.height-1; y++ {
s.setCell(position{0, y}, border)
s.setCell(position{s.width - 1, y}, border)
}
}
 
func (s *snake) placeFood() {
for {
// a random empty cell
x := rand.Intn(s.width-2) + 1
y := rand.Intn(s.height-2) + 1
foodp := position{x, y}
r := s.getCellRune(foodp)
if r != ' ' {
continue
}
s.setCell(foodp, food)
return
}
}
 
func (s *snake) startSnake() {
// a random cell somewhat near the center
x := rand.Intn(s.width/2) + s.width/4
y := rand.Intn(s.height/2) + s.height/4
head := position{x, y}
s.setCell(head, snakeHead)
s.body = append(s.body[:0], head)
s.heading = direction(rand.Intn(4))
}
 
func (s *snake) doMove(move direction) bool {
head := s.body[len(s.body)-1]
s.setCell(head, snakeBody)
head = head.next(move)
s.heading = move
s.body = append(s.body, head)
r := s.getCellRune(head)
s.setCell(head, snakeHead)
gameOver := false
switch r {
case food.Ch:
s.placeFood()
case border.Ch, snakeBody.Ch:
gameOver = true
fallthrough
case empty.Ch:
s.setCell(s.body[0], empty)
s.body = s.body[1:]
default:
panic(r)
}
s.flush()
return gameOver
}
 
const (
fg = termbox.ColorWhite
bg = termbox.ColorBlack
)
 
// Symbols to use.
// Could use Unicode instead of simple ASCII.
var (
empty = termbox.Cell{Ch: ' ', Bg: bg, Fg: fg}
border = termbox.Cell{Ch: '+', Bg: bg, Fg: termbox.ColorBlue}
snakeBody = termbox.Cell{Ch: '#', Bg: bg, Fg: termbox.ColorGreen}
snakeHead = termbox.Cell{Ch: 'O', Bg: bg, Fg: termbox.ColorYellow | termbox.AttrBold}
food = termbox.Cell{Ch: '@', Bg: bg, Fg: termbox.ColorRed}
)

Haskell[edit]

{-# LANGUAGE TemplateHaskell #-}
import Control.Monad.Random (getRandomRs)
import Graphics.Gloss.Interface.Pure.Game
import Lens.Micro ((%~), (^.), (&), set)
import Lens.Micro.TH (makeLenses)
 
--------------------------------------------------------------------------------
-- all data types
 
data Snake = Snake { _body :: [Point], _direction :: Point }
makeLenses ''Snake
 
data World = World { _snake :: Snake , _food :: [Point]
, _score :: Int , _maxScore :: Int }
makeLenses ''World
 
--------------------------------------------------------------------------------
-- everything snake can do
 
moves (Snake b d) = Snake (step b d : init b) d
eats (Snake b d) = Snake (step b d : b) d
bites (Snake b _) = any (== head b)
step ((x,y):_) (a,b) = (x+a, y+b)
 
turn (x',y') (Snake b (x,y)) | (x+x',y+y') == (0,0) = Snake b (x,y)
| otherwise = Snake b (x',y')
 
--------------------------------------------------------------------------------
-- all randomness
 
createWorld = do xs <- map fromIntegral <$> getRandomRs (2, 38 :: Int)
ys <- map fromIntegral <$> getRandomRs (2, 38 :: Int)
return (Ok, World snake (zip xs ys) 0 0)
where
snake = Snake [(20, 20)] (1,0)
 
-------------------------------------------------------------------------------
-- A tyny DSL for declarative description of business logic
 
data Status = Ok | Fail | Stop
 
continue = \x -> (Ok, x)
stop = \x -> (Stop, x)
f >>> g = \x -> case f x of { (Ok, y) -> g y; b -> b } -- chain composition
f <|> g = \x -> case f x of { (Fail, _) -> g x; b -> b } -- alternative
p ==> f = \x -> if p x then f x else (Fail, x) -- condition
l .& f = continue . (l %~ f) -- modification
l .= y = continue . set l y -- setting
 
--------------------------------------------------------------------------------
-- all business logic
 
updateWorld _ = id >>> (snakeEats <|> snakeMoves)
where
snakeEats = (snakeFindsFood ==> (snake .& eats)) >>>
(score .& (+1)) >>> (food .& tail)
 
snakeMoves = (snakeBitesTail ==> stop) <|>
(snakeHitsWall ==> stop) <|>
(snake .& moves)
 
snakeFindsFood w = (w^.snake & moves) `bites` (w^.food & take 1)
snakeBitesTail w = (w^.snake) `bites` (w^.snake.body & tail)
snakeHitsWall w = (w^.snake.body) & head & isOutside
isOutside (x,y) = or [x <= 0, 40 <= x, y <= 0, 40 <= y]
 
--------------------------------------------------------------------------------
-- all event handing
 
handleEvents e (s,w) = f w
where f = case s of
Ok -> case e of
EventKey (SpecialKey k) _ _ _ -> case k of
KeyRight -> snake .& turn (1,0)
KeyLeft -> snake .& turn (-1,0)
KeyUp -> snake .& turn (0,1)
KeyDown -> snake .& turn (0,-1)
_-> continue
_-> continue
_-> \w -> w & ((snake.body) .= [(20,20)]) >>>
(maxScore .& max (w^.score)) >>> (score .= 0)
 
--------------------------------------------------------------------------------
-- all graphics
 
renderWorld (s, w) = pictures [frame, color c drawSnake, drawFood, showScore]
where c = case s of { Ok -> orange; _-> red }
drawSnake = foldMap (rectangleSolid 10 10 `at`) (w^.snake.body)
drawFood = color blue $ circleSolid 5 `at` (w^.food & head)
frame = color black $ rectangleWire 400 400
showScore = color orange $ scale 0.2 0.2 $ txt `at` (-80,130)
txt = Text $ mconcat ["Score: ", w^.score & show
," Maximal score: ", w^.maxScore & show]
at p (x,y) = Translate (10*x-200) (10*y-200) p
 
--------------------------------------------------------------------------------
 
main = do world <- createWorld
play inW white 7 world renderWorld handleEvents updateWorld
where inW = InWindow "The Snake" (400, 400) (10, 10)

Extra credit

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

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

and add local definition:

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

Java[edit]

See Snake/Java.

JavaScript[edit]

You need the P5 Library to run this code!

 
const L = 1, R = 2, D = 4, U = 8;
var block = 24, wid = 30, hei = 20, frameR = 7, fruit, snake;
function Snake() {
this.length = 1;
this.alive = true;
this.pos = createVector( 1, 1 );
this.posArray = [];
this.posArray.push( createVector( 1, 1 ) );
this.dir = R;
this.draw = function() {
fill( 130, 190, 0 );
var pos, i = this.posArray.length - 1, l = this.length;
while( true ){
pos = this.posArray[i--];
rect( pos.x * block, pos.y * block, block, block );
if( --l == 0 ) break;
}
}
this.eat = function( frut ) {
var b = this.pos.x == frut.x && this.pos.y == frut.y;
if( b ) this.length++;
return b;
}
this.overlap = function() {
var len = this.posArray.length - 1;
for( var i = len; i > len - this.length; i-- ) {
tp = this.posArray[i];
if( tp.x === this.pos.x && tp.y === this.pos.y ) return true;
}
return false;
}
this.update = function() {
if( !this.alive ) return;
switch( this.dir ) {
case L:
this.pos.x--; if( this.pos.x < 1 ) this.pos.x = wid - 2;
break;
case R:
this.pos.x++; if( this.pos.x > wid - 2 ) this.pos.x = 1;
break;
case U:
this.pos.y--; if( this.pos.y < 1 ) this.pos.y = hei - 2;
break;
case D:
this.pos.y++; if( this.pos.y > hei - 2 ) this.pos.y = 1;
break;
}
if( this.overlap() ) { this.alive = false; } else {
this.posArray.push( createVector( this.pos.x, this.pos.y ) );
if( this.posArray.length > 5000 ) { this.posArray.splice( 0, 1 ); }
}
}
}
function Fruit() {
this.fruitTime = true;
this.pos = createVector();
this.draw = function() {
fill( 200, 50, 20 );
rect( this.pos.x * block, this.pos.y * block, block, block );
}
 
this.setFruit = function() {
this.pos.x = floor( random( 1, wid - 1 ) );
this.pos.y = floor( random( 1, hei - 1 ) );
this.fruitTime = false;
}
}
function setup() {
createCanvas( block * wid, block * hei );
noStroke(); frameRate( frameR );
snake = new Snake();fruit = new Fruit();
}
function keyPressed() {
switch( keyCode ) {
case LEFT_ARROW: snake.dir = L; break;
case RIGHT_ARROW: snake.dir = R; break;
case UP_ARROW: snake.dir = U; break;
case DOWN_ARROW: snake.dir = D;
}
}
function draw() {
background( color( 0, 0x22, 0 ) );
fill( 20, 50, 120 );
for( var i = 0; i < wid; i++ ) {
rect( i * block, 0, block, block );
rect( i * block, height - block, block, block );
}
for( var i = 1; i < hei - 1; i++ ) {
rect( 1, i * block, block, block );
rect( width - block, i * block, block, block );
}
if( fruit.fruitTime ) {
fruit.setFruit();
frameR += .2;
frameRate( frameR );
}
fruit.draw();
snake.update();
if( snake.eat( fruit.pos ) ) {
fruit.fruitTime = true;
}
snake.draw();
fill( 200 );
textStyle( BOLD ); textAlign( RIGHT ); textSize( 120 );
text( ""+( snake.length - 1 ), 690, 440 );
if( !snake.alive ) text( "THE END", 630, 250 );
}
 

Julia[edit]

Makie version in 99 lines.

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, [email protected](minimum($grid_size)))
 
food_position = @lift(($game.food.I .- (0.5,0.5)) .* $grid_size)
scatter!(scene, food_position, color=:red, marker='♥', [email protected](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()
 

Kotlin[edit]

Translation of: C++
Works with: Windows 10
// Kotlin Native v0.5
 
import kotlinx.cinterop.*
import platform.posix.*
import platform.windows.*
 
const val WID = 60
const val HEI = 30
const val MAX_LEN = 600
const val NUL = '\u0000'
 
enum class Dir { NORTH, EAST, SOUTH, WEST }
 
class Snake {
val console: HANDLE
var alive = false
val brd = CharArray(WID * HEI)
var dir = Dir.NORTH
val snk = nativeHeap.allocArray<COORD>(MAX_LEN)
lateinit var head: COORD
var tailIdx = 0
var headIdx = 0
var points = 0
 
init {
console = GetStdHandle(STD_OUTPUT_HANDLE)!!
SetConsoleTitleW("Snake")
memScoped {
val coord = alloc<COORD>().apply { X = (WID + 1).toShort(); Y = (HEI + 2).toShort() }
SetConsoleScreenBufferSize(console, coord.readValue())
val rc = alloc<SMALL_RECT>().apply {
Left = 0; Top = 0; Right = WID.toShort(); Bottom = (HEI + 1).toShort()
}
SetConsoleWindowInfo(console, TRUE, rc.ptr)
val ci = alloc<CONSOLE_CURSOR_INFO>().apply { dwSize = 1; bVisible = FALSE }
SetConsoleCursorInfo(console, ci.ptr)
}
}
 
fun play() {
while (true) {
createfield()
alive = true
while (alive) {
drawfield()
readKey()
moveSnake()
Sleep(50)
}
memScoped {
val c = alloc<COORD>().apply { X = 0; Y = (HEI + 1).toShort() }
SetConsoleCursorPosition(console, c.readValue())
}
SetConsoleTextAttribute(console, 0x000b)
print("Play again [Y/N]? ")
val a = readLine()!!.toLowerCase()
if (a.length > 0 && a[0] != 'y') {
nativeHeap.free(snk)
return
}
}
}
 
private fun createfield() {
memScoped {
val coord = alloc<COORD>().apply { X = 0; Y = 0 }
val c = alloc<DWORDVar>()
FillConsoleOutputCharacterW(console, 32, (HEI + 2) * 80, coord.readValue(), c.ptr)
FillConsoleOutputAttribute(console, 0x0000, (HEI + 2) * 80, coord.readValue(), c.ptr)
SetConsoleCursorPosition(console, coord.readValue())
}
for (x in 0 until WID * HEI) brd[x] = NUL
for (x in 0 until WID) {
brd[x + WID * (HEI - 1)] = '+'
brd[x] = '+'
}
for (y in 1 until HEI) {
brd[WID - 1 + WID * y] = '+'
brd[WID * y] = '+'
}
var xx: Int
var yy: Int
do {
xx = rand() % WID
yy = rand() % (HEI shr 1) + (HEI shr 1)
}
while (brd[xx + WID * yy] != NUL)
brd[xx + WID * yy] = '@'
tailIdx = 0
headIdx = 4
xx = 3
yy = 2
for (cc in tailIdx until headIdx) {
brd[xx + WID * yy] = '#'
snk[cc].X = (3 + cc).toShort()
snk[cc].Y = 2
}
head = snk[3]
dir = Dir.EAST
points = 0
}
 
private fun readKey() {
if ((GetAsyncKeyState(39).toInt() and 0x8000) != 0) dir = Dir.EAST
if ((GetAsyncKeyState(37).toInt() and 0x8000) != 0) dir = Dir.WEST
if ((GetAsyncKeyState(38).toInt() and 0x8000) != 0) dir = Dir.NORTH
if ((GetAsyncKeyState(40).toInt() and 0x8000) != 0) dir = Dir.SOUTH
}
 
private fun drawfield() {
memScoped {
val coord = alloc<COORD>()
var t = NUL
for (y in 0 until HEI) {
coord.Y = y.toShort()
for (x in 0 until WID) {
t = brd[x + WID * y]
if (t == NUL) continue
coord.X = x.toShort()
SetConsoleCursorPosition(console, coord.readValue())
if (coord.X == head.X && coord.Y == head.Y) {
SetConsoleTextAttribute(console, 0x002e)
print('O')
SetConsoleTextAttribute(console, 0x0000)
continue
}
when (t) {
'#' -> SetConsoleTextAttribute(console, 0x002a)
'+' -> SetConsoleTextAttribute(console, 0x0019)
'@' -> SetConsoleTextAttribute(console, 0x004c)
}
print(t)
SetConsoleTextAttribute(console, 0x0000)
}
}
print(t)
SetConsoleTextAttribute(console, 0x0007)
val c = alloc<COORD>().apply { X = 0; Y = HEI.toShort() }
SetConsoleCursorPosition(console, c.readValue())
print("Points: $points")
}
}
 
private fun moveSnake() {
when (dir) {
Dir.NORTH -> head.Y--
Dir.EAST -> head.X++
Dir.SOUTH -> head.Y++
Dir.WEST -> head.X--
}
val t = brd[head.X + WID * head.Y]
if (t != NUL && t != '@') {
alive = false
return
}
brd[head.X + WID * head.Y] = '#'
snk[headIdx].X = head.X
snk[headIdx].Y = head.Y
if (++headIdx >= MAX_LEN) headIdx = 0
if (t == '@') {
points++
var x: Int
var y: Int
do {
x = rand() % WID
y = rand() % (HEI shr 1) + (HEI shr 1)
}
while (brd[x + WID * y] != NUL)
brd[x + WID * y] = '@'
return
}
SetConsoleCursorPosition(console, snk[tailIdx].readValue())
print(' ')
brd[snk[tailIdx].X + WID * snk[tailIdx].Y] = NUL
if (++tailIdx >= MAX_LEN) tailIdx = 0
}
}
 
fun main(args: Array<String>) {
srand(time(null).toInt())
Snake().play()
}
Output:
Similar to C++ entry

Nim[edit]

Translation of: C
Library: nim-ncurses

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

import macros, os, random
import ncurses
 
when defined(Linux):
proc positional_putch(x, y: int; ch: char) = mvaddch(x.cint, y.cint, ch.chtype)
proc updateScreen = refresh()
proc nonBlockingGetch(): char =
let c = getch()
result = if c in 0..255: char(c) else: '\0'
proc closeScreen = endwin()
 
else:
error "Not implemented"
 
const
 
W = 80
H = 40
Space = 0
Food = 1
Border = 2
Symbol = [' ', '@', '.']
 
type
 
Dir {.pure.} = enum North, East, South, West
Game = object
board: array[W * H, int]
head: int
dir: Dir
quit: bool
 
 
proc age(game: var Game) =
## Reduce a time-to-live, effectively erasing the tail.
for i in 0..<W*H:
if game.board[i] < 0: inc game.board[i]
 
 
proc plant(game: var Game) =
## Put a piece of food at random empty position.
var r: int
while true:
r = rand(W * H - 1)
if game.board[r] == Space: break
game.board[r] = Food
 
 
proc start(game: var Game) =
## Initialize the board, plant a very first food item.
for i in 0..<W:
game.board[i] = Border
game.board[i + (H - 1) * W] = Border
for i in 0..<H:
game.board[i * W] = Border
game.board[i * W + W - 1] = Border
game.head = W * (H - 1 - (H and 1)) shr 1 # Screen center for any H.
game.board[game.head] = -5
game.dir = North
game.quit = false
game.plant()
 
 
proc step(game: var Game) =
let len = game.board[game.head]
case game.dir
of North: dec game.head, W
of South: inc game.head, W
of West: dec game.head
of East: inc game.head
 
case game.board[game.head]
of Space:
game.board[game.head] = len - 1 # Keep in mind "len" is negative.
game.age()
of Food:
game.board[game.head] = len - 1
game.plant()
else:
game.quit = true
 
 
proc show(game: Game) =
for i in 0..<W*H:
positionalPutch(i div W, i mod W, if game.board[i] < 0: '#' else: Symbol[game.board[i]])
updateScreen()
 
 
var game: Game
randomize()
let win = initscr()
cbreak() # Make sure thre is no buffering.
noecho() # Suppress echoing of characters.
nodelay(win, true) # Non-blocking mode.
game.start()
 
while not game.quit:
game.show()
case nonBlockingGetch()
of 'i': game.dir = North
of 'j': game.dir = West
of 'k': game.dir = South
of 'l': game.dir = East
of 'q': game.quit = true
else: discard
game.step()
os.sleep(300) # Adjust here: 100 is very fast.
 
sleep(1000)
closeScreen()

OCaml[edit]

Library: OCamlSDL2
(* A simple Snake Game *)
open Sdl
 
let width, height = (640, 480)
 
type pos = int * int
 
type game_state = {
pos_snake: pos;
seg_snake: pos list;
dir_snake: [`left | `right | `up | `down];
pos_fruit: pos;
sleep_time: int;
game_over: bool;
}
 
let red = (255, 0, 0)
let blue = (0, 0, 255)
let green = (0, 255, 0)
let black = (0, 0, 0)
let alpha = 255
 
let fill_rect renderer (x, y) =
let rect = Rect.make4 x y 20 20 in
Render.fill_rect renderer rect;
;;
 
 
let display_game renderer state =
let bg_color, snake_color, fruit_color =
if state.game_over
then (red, black, green)
else (black, blue, red)
in
Render.set_draw_color renderer bg_color alpha;
Render.clear renderer;
Render.set_draw_color renderer fruit_color alpha;
fill_rect renderer state.pos_fruit;
Render.set_draw_color renderer snake_color alpha;
List.iter (fill_rect renderer) state.seg_snake;
Render.render_present renderer;
;;
 
 
let proc_events dir_snake = function
| Event.KeyDown { Event.keycode = Keycode.Left } -> `left
| Event.KeyDown { Event.keycode = Keycode.Right } -> `right
| Event.KeyDown { Event.keycode = Keycode.Up } -> `up
| Event.KeyDown { Event.keycode = Keycode.Down } -> `down
| Event.KeyDown { Event.keycode = Keycode.Q }
| Event.KeyDown { Event.keycode = Keycode.Escape }
| Event.Quit _ -> Sdl.quit (); exit 0
| _ -> (dir_snake)
 
 
let rec event_loop dir_snake =
match Event.poll_event () with
| None -> (dir_snake)
| Some ev ->
let dir = proc_events dir_snake ev in
event_loop dir
 
 
let rec pop = function
| [_] -> []
| hd :: tl -> hd :: (pop tl)
| [] -> invalid_arg "pop"
 
 
let rec new_pos_fruit seg_snake =
let new_pos =
(20 * Random.int 32,
20 * Random.int 24)
in
if List.mem new_pos seg_snake
then new_pos_fruit seg_snake
else (new_pos)
 
 
let update_state req_dir ({
pos_snake;
seg_snake;
pos_fruit;
dir_snake;
sleep_time;
game_over;
} as state) =
if game_over then state else
let dir_snake =
match dir_snake, req_dir with
| `left, `right -> dir_snake
| `right, `left -> dir_snake
| `up, `down -> dir_snake
| `down, `up -> dir_snake
| _ -> req_dir
in
let pos_snake =
let x, y = pos_snake in
match dir_snake with
| `left -> (x - 20, y)
| `right -> (x + 20, y)
| `up -> (x, y - 20)
| `down -> (x, y + 20)
in
let game_over =
let x, y = pos_snake in
List.mem pos_snake seg_snake
|| x < 0 || y < 0
|| x >= width
|| y >= height
in
let seg_snake = pos_snake :: seg_snake in
let seg_snake, pos_fruit, sleep_time =
if pos_snake = pos_fruit
then (seg_snake, new_pos_fruit seg_snake, sleep_time - 1)
else (pop seg_snake, pos_fruit, sleep_time)
in
{ pos_snake;
seg_snake;
pos_fruit;
dir_snake;
sleep_time;
game_over;
}
 
 
let () =
Random.self_init ();
Sdl.init [`VIDEO];
let window, renderer =
Render.create_window_and_renderer ~width ~height ~flags:[]
in
Window.set_title ~window ~title:"Snake OCaml-SDL2";
let initial_state = {
pos_snake = (100, 100);
seg_snake = [
(100, 100);
( 80, 100);
( 60, 100);
];
pos_fruit = (200, 200);
dir_snake = `right;
sleep_time = 120;
game_over = false;
} in
 
let rec main_loop state =
let req_dir = event_loop state.dir_snake in
let state = update_state req_dir state in
display_game renderer state;
Timer.delay state.sleep_time;
main_loop state
in
main_loop initial_state

Perl[edit]

Snake game perl.png
use utf8;
use Time::HiRes qw(sleep);
use Term::ANSIColor qw(colored);
use Term::ReadKey qw(ReadMode ReadLine);
 
binmode(STDOUT, ':utf8');
 
use constant {
VOID => 0,
HEAD => 1,
BODY => 2,
TAIL => 3,
FOOD => 4,
};
 
use constant {
LEFT => [+0, -1],
RIGHT => [+0, +1],
UP => [-1, +0],
DOWN => [+1, +0],
};
 
use constant {
BG_COLOR => "on_black",
SLEEP_SEC => 0.05,
};
 
use constant {
SNAKE_COLOR => ('bold green' . ' ' . BG_COLOR),
FOOD_COLOR => ('red' . ' ' . BG_COLOR),
};
 
use constant {
U_HEAD => colored('▲', SNAKE_COLOR),
D_HEAD => colored('▼', SNAKE_COLOR),
L_HEAD => colored('◀', SNAKE_COLOR),
R_HEAD => colored('▶', SNAKE_COLOR),
 
U_BODY => colored('╹', SNAKE_COLOR),
D_BODY => colored('╻', SNAKE_COLOR),
L_BODY => colored('╴', SNAKE_COLOR),
R_BODY => colored('╶', SNAKE_COLOR),
 
U_TAIL => colored('╽', SNAKE_COLOR),
D_TAIL => colored('╿', SNAKE_COLOR),
L_TAIL => colored('╼', SNAKE_COLOR),
R_TAIL => colored('╾', SNAKE_COLOR),
 
A_VOID => colored(' ', BG_COLOR),
A_FOOD => colored('❇', FOOD_COLOR),
};
 
local $| = 1;
 
my $w = eval { `tput cols` } || 80;
my $h = eval { `tput lines` } || 24;
my $r = "\033[H";
 
my @grid = map {
[map { [VOID] } 1 .. $w]
} 1 .. $h;
 
my $dir = LEFT;
my @head_pos = ($h / 2, $w / 2);
my @tail_pos = ($head_pos[0], $head_pos[1] + 1);
 
$grid[$head_pos[0]][$head_pos[1]] = [HEAD, $dir]; # head
$grid[$tail_pos[0]][$tail_pos[1]] = [TAIL, $dir]; # tail
 
sub create_food {
my ($food_x, $food_y);
 
do {
$food_x = rand($w);
$food_y = rand($h);
} while ($grid[$food_y][$food_x][0] != VOID);
 
$grid[$food_y][$food_x][0] = FOOD;
}
 
create_food();
 
sub display {
my $i = 0;
 
print $r, join("\n",
map {
join("",
map {
my $t = $_->[0];
if ($t != FOOD and $t != VOID) {
my $p = $_->[1];
$i =
$p eq UP ? 0
: $p eq DOWN ? 1
: $p eq LEFT ? 2
: 3;
}
$t == HEAD ? (U_HEAD, D_HEAD, L_HEAD, R_HEAD)[$i]
: $t == BODY ? (U_BODY, D_BODY, L_BODY, R_BODY)[$i]
: $t == TAIL ? (U_TAIL, D_TAIL, L_TAIL, R_TAIL)[$i]
: $t == FOOD ? (A_FOOD)
: (A_VOID);
 
} @{$_}
)
} @grid
);
}
 
sub move {
my $grew = 0;
 
# Move the head
{
my ($y, $x) = @head_pos;
 
my $new_y = ($y + $dir->[0]) % $h;
my $new_x = ($x + $dir->[1]) % $w;
 
my $cell = $grid[$new_y][$new_x];
my $t = $cell->[0];
 
if ($t == BODY or $t == TAIL) {
die "Game over!\n";
}
elsif ($t == FOOD) {
create_food();
$grew = 1;
}
 
# Create a new head
$grid[$new_y][$new_x] = [HEAD, $dir];
 
# Replace the current head with body
$grid[$y][$x] = [BODY, $dir];
 
# Save the position of the head
@head_pos = ($new_y, $new_x);
}
 
# Move the tail
if (not $grew) {
my ($y, $x) = @tail_pos;
 
my $pos = $grid[$y][$x][1];
my $new_y = ($y + $pos->[0]) % $h;
my $new_x = ($x + $pos->[1]) % $w;
 
$grid[$y][$x][0] = VOID; # erase the current tail
$grid[$new_y][$new_x][0] = TAIL; # create a new tail
 
# Save the position of the tail
@tail_pos = ($new_y, $new_x);
}
}
 
ReadMode(3);
while (1) {
my $key;
until (defined($key = ReadLine(-1))) {
move();
display();
sleep(SLEEP_SEC);
}
 
if ($key eq "\e[A" and $dir ne DOWN ) { $dir = UP }
elsif ($key eq "\e[B" and $dir ne UP ) { $dir = DOWN }
elsif ($key eq "\e[C" and $dir ne LEFT ) { $dir = RIGHT }
elsif ($key eq "\e[D" and $dir ne RIGHT) { $dir = LEFT }
}

Phix[edit]

Translation of: C++
constant W = 60, H = 30, MAX_LEN = 600
enum NORTH, EAST, SOUTH, WEST
 
sequence board, snake
bool alive
integer tailIdx, headIdx, hdX, hdY, d, points
 
procedure createField()
clear_screen()
board = repeat("+"&repeat(' ',W-2)&'+',H)
for x=1 to W do
board[1,x] = '+'
end for
board[H] = board[1]
board[1+rand(H-2),1+rand(W-2)] = '@';
snake = repeat(0,MAX_LEN)
board[3,4] = '#'; tailIdx = 1; headIdx = 5;
for c=tailIdx to headIdx do
snake[c] = {3,3+c}
end for
{hdY,hdX} = snake[headIdx-1]; d = EAST; points = 0;
end procedure
 
procedure drawField()
for y=1 to H do
for x=1 to W do
integer t = board[y,x]
if t!=' ' then
position(y,x)
if x=hdX and y=hdY then
text_color(14); puts(1,'O');
else
text_color({10,9,12}[find(t,"#[email protected]")]); 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()

Raku[edit]

(formerly Perl 6)

Works with: Rakudo version 2016.08

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

use SDL2::Raw;
use Cairo;
 
constant W = 1280;
constant H = 960;
 
constant FIELDW = W div 32;
constant FIELDH = H div 32;
 
SDL_Init(VIDEO);
 
my $window = SDL_CreateWindow(
'Snake',
SDL_WINDOWPOS_CENTERED_MASK,
SDL_WINDOWPOS_CENTERED_MASK,
W, H,
OPENGL
);
 
my $render = SDL_CreateRenderer($window, -1, ACCELERATED +| PRESENTVSYNC);
 
my $snake_image = Cairo::Image.record(
-> $_ {
.save;
.rectangle: 0, 0, 64, 64;
.clip;
.rgb: 0, 1, 0;
.rectangle: 0, 0, 64, 64;
.fill :preserve;
.rgb: 0, 0, 0;
.stroke;
.restore;
 
.save;
.translate: 64, 0;
.rectangle: 0, 0, 64, 64;
.clip;
.rgb: 1, 0, 0;
.arc: 32, 32, 30, 0, 2 * pi;
.fill :preserve;
.rgb: 0, 0, 0;
.stroke;
.restore;
}, 128, 128, Cairo::FORMAT_ARGB32);
 
my $snake_texture = SDL_CreateTexture(
$render,
%PIXELFORMAT<ARGB8888>,
STATIC,
128,
128
);
 
SDL_UpdateTexture(
$snake_texture,
SDL_Rect.new(
:x(0),
:y(0),
:w(128),
:h(128)
),
$snake_image.data,
$snake_image.stride // 128 * 4
);
 
SDL_SetTextureBlendMode($snake_texture, 1);
 
SDL_SetRenderDrawBlendMode($render, 1);
 
my $snakepiece_srcrect = SDL_Rect.new(:w(64), :h(64));
my $nompiece_srcrect = SDL_Rect.new(:w(64), :h(64), :x(64));
 
my $event = SDL_Event.new;
 
enum GAME_KEYS (
K_UP => 82,
K_DOWN => 81,
K_LEFT => 80,
K_RIGHT => 79,
);
 
my Complex @snakepieces = 10+10i;
my Complex @noms;
my Complex $snakedir = 1+0i;
my $nomspawn = 0;
my $snakespeed = 0.1;
my $snakestep = 0;
my $nom = 4;
 
my $last_frame_start = now;
main: loop {
my $start = now;
my $dt = $start - $last_frame_start // 0.00001;
while SDL_PollEvent($event) {
my $casted_event = SDL_CastEvent($event);
given $casted_event {
when *.type == QUIT { last main }
when *.type == KEYDOWN {
if GAME_KEYS(.scancode) -> $comm {
given $comm {
when 'K_LEFT' { $snakedir = -1+0i unless $snakedir == 1+0i }
when 'K_RIGHT' { $snakedir = 1+0i unless $snakedir == -1+0i }
when 'K_UP' { $snakedir = 0-1i unless $snakedir == 0+1i }
when 'K_DOWN' { $snakedir = 0+1i unless $snakedir == 0-1i }
}
}
}
}
}
 
if ($nomspawn -= $dt) < 0 {
$nomspawn += 1;
@noms.push: (^FIELDW).pick + (^FIELDH).pick * i unless @noms > 3;
@noms.pop if @noms[*-1] == any(@snakepieces);
}
 
if ($snakestep -= $dt) < 0 {
$snakestep += $snakespeed;
 
@snakepieces.unshift: do given @snakepieces[0] {
($_.re + $snakedir.re) % FIELDW
+ (($_.im + $snakedir.im) % FIELDH) * i
}
 
if @snakepieces[2..*].first( * == @snakepieces[0], :k ) -> $idx {
@snakepieces = @snakepieces[0..($idx + 1)];
}
 
@noms .= grep(
{ $^piece == @snakepieces[0] ?? ($nom += 1) && False !! True }
);
 
if $nom == 0 {
@snakepieces.pop;
} else {
$nom = $nom - 1;
}
}
 
for @snakepieces {
SDL_SetTextureColorMod(
$snake_texture,
255,
(cos((++$) / 2) * 100 + 155).round,
255
);
 
SDL_RenderCopy(
$render,
$snake_texture,
$snakepiece_srcrect,
SDL_Rect.new(.re * 32, .im * 32, 32, 32)
);
}
 
SDL_SetTextureColorMod($snake_texture, 255, 255, 255);
 
for @noms {
SDL_RenderCopy(
$render,
$snake_texture,
$nompiece_srcrect,
SDL_Rect.new(.re * 32, .im * 32, 32, 32)
)
}
 
SDL_RenderPresent($render);
SDL_SetRenderDrawColor($render, 0, 0, 0, 0);
SDL_RenderClear($render);
 
$last_frame_start = $start;
sleep(1 / 50);
}
 
SDL_Quit();

Sidef[edit]

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

XPL0[edit]

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

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

def     Width=40, Height=25-1,          \playing area including border
StartLength = 10, \starting length of snake including head
Morsels = 10; \number of food items constantly offered
int Heading; \direction snake is heading
def Up, Down, Left, Right;
int Length, \current length of snake including head
Score; \number of food items eaten
char SnakeX(10000), \location of each segment including head
SnakeY(10000), \ample room to grow
FoodX(Morsels), FoodY(Morsels); \location of each food item
def Black, Blue, Green, Cyan, Red, Magenta, Brown, White, \attribute colors
Gray, LBlue, LGreen, LCyan, LRed, LMagenta, Yellow, BWhite; \EGA palette
 
proc PlaceFood(N); \Place Nth food item in playing area
int N;
[FoodX(N):= Ran(Width-3) + 1; \pick random location inside borders
FoodY(N):= Ran(Height-3) + 1;
Cursor(FoodX(N), FoodY(N)); \show food
Attrib(Red<<4+LRed);
ChOut(6, ^@);
]; \PlaceFood
 
 
int X, Y, I, C;
[SetVid($01); \set 40x25 text mode
ShowCursor(false); \turn off flashing cursor
 
Attrib(Blue<<4+LBlue); \show borders
Cursor(0, 0);
for X:= 0 to Width-1 do ChOut(6, ^+);
Cursor(0, Height-1);
for X:= 0 to Width-1 do ChOut(6, ^+);
for Y:= 0 to Height-1 do
[Cursor(0, Y); ChOut(6, ^+);
Cursor(Width-1, Y); ChOut(6, ^+);
];
Attrib(Black<<4+White); \show initial score
Cursor(0, 24);
Text(6, "Score: 0");
Score:= 0;
 
SnakeX(0):= Width/2; \start snake head at center
SnakeY(0):= Height/2;
Heading:= Left;
Length:= StartLength;
for I:= 1 to Length-1 do \segments follow head to the right
[SnakeX(I):= SnakeX(I-1) + 1;
SnakeY(I):= SnakeY(I-1);
];
for I:= 0 to Morsels-1 do PlaceFood(I); \sow some tasty food
 
\Continuously move snake
loop \--------------------------------------------------------------------------
[Attrib(Black<<4+White); \remove tail-end segment
Cursor(SnakeX(Length-1), SnakeY(Length-1));
ChOut(6, ^ );
Attrib(Green<<4+Yellow); \add segment at head location
Cursor(SnakeX(0), SnakeY(0));
ChOut(6, ^#);
 
\Shift coordinates toward tail (+1 in case a segment gets added)
for I:= Length downto 1 do \segment behind head gets head's coords
[SnakeX(I):= SnakeX(I-1);
SnakeY(I):= SnakeY(I-1);
];
if ChkKey then \key hit--get new movement direction
[repeat C:= ChIn(1) until C # 0; \remove arrow keys' prefix byte
case C of
$1B: exit; \Escape, and scan codes for arrow keys
$48: if Heading # Down then Heading:= Up;
$50: if Heading # Up then Heading:= Down;
$4B: if Heading # Right then Heading:= Left;
$4D: if Heading # Left then Heading:= Right
other []; \ignore any other keystrokes
];
case Heading of \move head to its new location
Up: SnakeY(0):= SnakeY(0)-1;
Down: SnakeY(0):= SnakeY(0)+1;
Left: SnakeX(0):= SnakeX(0)-1;
Right:SnakeX(0):= SnakeX(0)+1
other [];
Cursor(SnakeX(0), SnakeY(0)); \show head at its new location
ChOut(6, ^8);
 
for I:= 0 to Morsels-1 do
if SnakeX(0)=FoodX(I) & SnakeY(0)=FoodY(I) then
[Score:= Score+1; \ate a morsel
Attrib(Black<<4+White);
Cursor(7, 24);
IntOut(6, Score*10);
PlaceFood(I); \replenish morsel
Length:= Length+1; \grow snake one segment
I:= Morsels; \over eating can be bad--quit for loop
Sound(1, 1, 1500); \BURP!
Sound(1, 1, 1000);
];
if I = Morsels then Sound(0, 2, 1); \no sound adds delay of 2/18th second
 
if SnakeX(0)=0 or SnakeX(0)=Width-1 or
SnakeY(0)=0 or SnakeY(0)=Height-1 then
quit; \snake hit border--game over
for I:= 1 to Length-1 do
if SnakeX(0)=SnakeX(I) & SnakeY(0)=SnakeY(I) then
quit; \snake bit itself--game over
]; \loop -----------------------------------------------------------------------
for I:= 1 to 8 do Sound(1, 1, 4000*I); \death dirge
Sound(0, 36, 1); \pause 2 seconds to see result
OpenI(1); \flush any pending keystrokes
]