Snake: Difference between revisions
Alpha bravo (talk | contribs) Added AutoHotkey |
|||
(74 intermediate revisions by 20 users not shown) | |||
Line 1:
{{
{{wikipedia|Snake_(video_game)}}
<br><br>
[[wp:Snake_(video_game)|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.
The game ends when the snake attempts to eat himself.
<br><br>
=={{header|Ada}}==
<syntaxhighlight lang="ada">
-- This code is a basic implementation of snake in Ada using the command prompt
-- feel free to improve it!
-- Snake.ads
with Ada.Containers; use Ada.Containers;
with Ada.Containers.Vectors;
package Snake is
-- copy from 2048 game (
-- Keyboard management
type directions is (Up, Down, Right, Left, Quit, Restart, Invalid);
-- Redefining this standard procedure as function to allow Get_Keystroke as an expression function
function Get_Immediate return Character;
Arrow_Prefix : constant Character := Character'Val(224); -- works for windows
function Get_Keystroke return directions;
-- )
-- The container for game data
type gameBoard is array (Natural range <>, Natural range <>) of Character;
-- Initilize the board
procedure init;
-- Run the game
procedure run;
-- Displaying the board
procedure Display_Board;
-- Clear the board from content
procedure ClearBoard;
-- coordinates data structure
type coord is tagged record
X,Y : Integer;
end record;
-- list of coordinate (one coordinate for each body part of the snake)
package snakeBody is new Ada.Containers.Vectors (Natural, coord);
use snakeBody;
-- update snake's part depending on the snakeDirection and checking colicion (with borders and snak's part)
function moveSnake return Boolean;
-- generate food if it was eaten
procedure generateFood;
-- Add snake and food to the board
procedure addDataToBoard;
-- generate random integer between 1 and upperBound to generate random food position
function getRandomInteger(upperBound : Integer) return Integer;
private
width, height : Positive := 10;
board : gameBoard := (0 .. (width+1) => (0 .. (height+1) => ' '));
snake : Vector := (1,1) & (2,1) & (3,1);
snakeDirection : directions := Right;
food : coord := (5,5);
end Snake;
-- Snake.adb
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Numerics.Discrete_Random;
package body Snake is
-----------------------------------------------------------------------------
-- Redefining this standard procedure as function to allow Get_Keystroke as an expression function (copy from 2048 game)
function Get_Immediate return Character is
begin
return Answer : Character do Ada.Text_IO.Get_Immediate(Answer);
end return;
end Get_Immediate;
-----------------------------------------------------------------------------
-- (copy from 2048 game)
function Get_Keystroke return directions is
(case Get_Immediate is
when 'Q' | 'q' => Quit,
when 'R' | 'r' => Restart,
when 'W' | 'w' => Left,
when 'A' | 'a' => Up,
when 'S' | 's' => Down,
when 'D' | 'd' => Right,
-- Windows terminal
when Arrow_Prefix => (case Character'Pos(Get_Immediate) is
when 72 => Up,
when 75 => Left,
when 77 => Right,
when 80 => Down,
when others => Invalid),
-- Unix escape sequences
when ASCII.ESC => (case Get_Immediate is
when '[' => (case Get_Immediate is
when 'A' => Up,
when 'B' => Down,
when 'C' => Right,
when 'D' => Left,
when others => Invalid),
when others => Invalid),
when others => Invalid);
-----------------------------------------------------------------------------
procedure init is
begin
for Row in 0 .. width+1 loop
for Column in 0 .. height+1 loop
if Row = 0 or Row = width+1 then
board(Row,Column) := '#';
-- Insert(board(Row,0), Column, '#');
else
if Column = 0 or Column = height+1 then
board(Row,Column) := '#';
-- Insert(board, board(Row, Column), "#");
else
board(Row,Column) := ' ';
-- Insert(board, board(Row, Column), " ");
end if;
end if;
end loop;
end loop;
end;
-----------------------------------------------------------------------------
procedure run is
task T;
task body T is
begin
loop
snakeDirection := Get_Keystroke;
end loop;
end T;
begin
init;
loop
exit when snakeDirection = Quit;
if moveSnake = False then
Put_Line("GAME OVER!!!");
Put_Line("Your score is:" & Length(snake)'Image);
exit;
end if;
Display_Board;
delay 0.7;
end loop;
end run;
-----------------------------------------------------------------------------
procedure Display_Board is
begin
for Row in 0 .. width+1 loop
for Column in 0 .. height+1 loop
Put(board(Row, Column));
end loop;
New_Line;
end loop;
end Display_Board;
-----------------------------------------------------------------------------
procedure ClearBoard is
begin
for Row in 1 .. width loop
for Column in 1 .. height loop
board(Row, Column) := ' ';
end loop;
end loop;
end ClearBoard;
-----------------------------------------------------------------------------
function moveSnake return Boolean is
colision : Boolean := False;
headCoord : coord := Snake.First_Element;
addSnakePart : Boolean := False;
begin
case snakeDirection is
when Up => headCoord.X := headCoord.X - 1;
when Down => headCoord.X := headCoord.X + 1;
when Right => headCoord.Y := headCoord.Y + 1;
when Left => headCoord.Y := headCoord.Y - 1;
when others => null;
end case;
if headCoord.Y = height+1 then
return False;
end if;
if headCoord.Y = 0 then
return False;
end if;
if headCoord.X = width+1 then
return False;
end if;
if headCoord.X = 0 then
return False;
end if;
for index in snake.Iterate loop
if headCoord = snake(To_Index(index)) then
return False;
end if;
end loop;
snake.Prepend(headCoord);
if headCoord /= food then
snake.Delete_Last;
else
food := (0,0);
generateFood;
end if;
addDataToBoard;
return True;
end moveSnake;
-----------------------------------------------------------------------------
procedure generateFood is
begin
if food.X = 0 or food.Y = 0 then
food := (getRandomInteger(width), getRandomInteger(height));
end if;
end generateFood;
-----------------------------------------------------------------------------
procedure addDataToBoard is
begin
ClearBoard;
board(food.X, food.Y) := '*';
for index in snake.Iterate loop
board(snake(To_Index(index)).X, snake(To_Index(index)).Y) := 'o';
end loop;
end addDataToBoard;
-----------------------------------------------------------------------------
function getRandomInteger(upperBound : Integer) return Integer is
subtype Random_Range is Integer range 1 .. upperBound;
package R is new
Ada.Numerics.Discrete_Random (Random_Range);
use R;
G : Generator;
begin
Reset (G);
return Random (G);
end getRandomInteger;
end Snake;
-- main.adb
with Snake;
procedure Main is
begin
Snake.run;
end;
</syntaxhighlight>
{{out}}
<pre>
############
# #
# #
# ooo #
# o #
# o #
# o #
# o #
# o #
# * #
# #
############
</pre>
=={{header|Amazing Hopper}}==
{{trans|C}}
<syntaxhighlight 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. */
#include <flow.h>
#include <flow-term.h>
#include <keys.h>
#define LIMIT_TIME 120
#define INF_TIME 80 //70
#define ONE_SECOND 1000
#define TOTAL_TIME 100
#define DECREMENT_TIME 10
#define COLOR_FOOD 232
#define BACK_FOOD 255
#define TIME_LEVEL 90000 //90000 // un minuto y medio
#define TIME_CHALLENGE 40000 // 40 segundos
#define TIME_LOST 30000
#define COLUMNA_PRINT 55
#define SIZE_INITIAL -7
#enum 1,N,E,S,W
#enum 1,SPACE,FOOD,BORDER
DEF-MAIN
BREAK-ON
STACK 16
MSET(C, quit, nHead, dir, Size, SCORE, counter, T, TPlay,TSound, ConsPrey,Consumed stage )
SET(len,0)
SET( w, 50)
SET( h, 28)
SET( color food, COLOR_FOOD )
SET( back food, BACK_FOOD )
SET( TIME, 100 )
LET( Size := MUL(w,h) )
SET( LEVEL,1 )
SET( REAL LEVEL,1 )
FALSE(swPierde)
TRUE(swExtra1,swExtra2)
TRUE( tiles )
SET( back tiles,1 ), MSET(tile1, tile2)
VOID( head back tiles )
MEM("\033[48;5;28m","\033[48;5;29m","\033[48;5;62m","\033[48;5;63m","\033[48;5;70m","\033[48;5;71m","\033[48;5;4m","\033[48;5;27m" )
MEM("\033[48;5;99m","\033[48;5;97m","\033[48;5;17m","\033[48;5;18m","\033[48;5;62m","\033[48;5;63m")
APND-LST(head back tiles)
[back tiles] GET(head back tiles), MOVE-TO(tile1)
[PLUS-ONE(back tiles)] GET(head back tiles), MOVE-TO(tile2)
VOID( big number, numL1, numL2, numL3 )
VOID( sounds ), SET( sound index, 1 )
MEM("Snake_music.wav","Snake_music_l3.wav","Snake_music_l5.wav","Snake_music_l4.wav","Snake_music_l6.wav")
APND-LST(sounds)
GOSUB( set score )
MSET(tBoard1,tmpLimit,Maze,wM,hM,SizeM)
DIM( Size ) AS-ONES( board ), {board} MOVE-TO(tBoard1)
// load and prepare maze for challenge stage:
SET(i,1)
LET( Maze := REPLICATE("3",50) )
PERF-UP(i,26,1)
LET( Maze := CAT( Maze, CAT( CAT("3", REPLICATE("1",48) ), "3") ) )
NEXT
LET( Maze := CAT( Maze, REPLICATE("3",50) ))
GOSUB( prepare maze )
HIDE-CURSOR
CLR-SCR
SET( TLimit := LIMIT_TIME )
MEM(SIZE_INITIAL),GOSUB( start )
SET( time out, TIME_LEVEL)
SYS( CAT(CAT("aplay fl/",[sound index]CGET(sounds)), "</dev/null >/dev/null 2>&1 &"))
GOSUB( titles )
/* PLAY GAME!! */
SET(lives,3)
WHILE( lives )
GOSUB( show )
GOSUB( put titles )
GOSUB( ready ), SLEEP(1.5)
TIC( T ), TIC( TPlay ), TIC(TSound)
KBD-FREE
WHILE( NOT (quit) )
ON-TIME( TLimit ~= TPlay ){
GOSUB( show )
WHEN( KEY-PRESSED? ){
SCAN-CODE( C )
SELECT( C )
CASE( K_UP ) { dir = N, EXIT }
CASE( K_RIGHT ){ dir = E, EXIT }
CASE( K_DOWN ) { dir = S, EXIT }
CASE( K_LEFT ) { dir = W, EXIT }
CASE( K_ESC ) { quit = 1, EXIT }
CASE( 32 ) { PAUSE, EXIT }
SELEND
}
GOSUB( step )
}
ON-TIME( ONE_SECOND ~= T ) {
TIME-=DECREMENT_TIME, CLAMP(0,TOTAL_TIME,TIME)
{TIME, 12, COLUMNA_PRINT} GOSUB( put time )
}
ON-TIME( time out ~= TSound ){
GOSUB(LEVELCLEAR)
}
WEND
COND( OR(EQ?(LEVEL,3),EQ?(LEVEL,6)) )
GOSUB(you lost),SLEEP(3)
SET(quit,0), [nHead]{len}CPUT(board)
LOCATE(1,1), FILL-BOX(" ",29,54)
GOSUB( another play )
ELS
GOSUB(you lost), SLEEP(3)
--lives
SET(quit,0)
TRUE(swPierde)
{len}GOSUB( start )
COND( IS-NOT-ZERO?(lives) )
SYS("aplay fl/Snake_music_vida_repechaje.wav </dev/null >/dev/null 2>&1 &")
CEND
CEND
WEND
/* GAME OVER */
GOSUB(game over ), SLEEP(7)
SET-VIDEO-TEXT
SHOW-CURSOR
PRNL
END
RUTINES
DEF-FUN( KILLSOUND )
SET( PID,0 )
LET( PID := ONLY-CHR("0123456789 ", EXEC( "pidof aplay" )))
COND( LEN(PID) )
SYS( CAT(CAT("kill ",PID), " </dev/null >/dev/null 2>&1"))
CEND
RET
// initialize the board, plant a very first food item
DEF-FUN( start, initial pos )
COND( NOT-EQ?(LEVEL,3) )
{tBoard1} MOVE-TO(board)
{50,28},MUL(50,28), MOVE-TO( w, h, Size )
[1:w] {BORDER} CPUT(board) // top
[ SUB(MUL(h,w),MINUS-ONE(w)) : end] {BORDER} CPUT(board) // bottom
[1:w:end] {BORDER} CPUT(board) // left
SET(i, 1)
FOR( LE?(i, h), ++i )
[ MUL(i, w )] {BORDER} PUT(board) // right
NEXT
LET( time out:=TIME_LEVEL )
ELS
TRASH(board)
CEND
SELECT(LEVEL)
CASE(2){
SET(i,3)
PERF-UP(i,5,1)
// laterales extremos:
[ADD(MUL(i,w),5):ADD(MUL(i,w),8)] {BORDER} CPUT(board)
[ADD(MUL(i,w),SUB(w,7)):ADD(MUL(i,w),SUB(w,4))] {BORDER} CPUT(board)
// laterales fondo
[ADD(MUL(ADD(i,19),w),5):ADD(MUL(ADD(i,19),w),8)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,19),w),SUB(w,7)):ADD(MUL(ADD(i,19),w),SUB(w,4))] {BORDER} CPUT(board)
NEXT
EXIT
}
CASE(3){ // challenge stage!
SPLIT(Maze,board, "" ), VAL(board), MOVE-TO(board)
[1:w] {BORDER}CPUT(board)
SET(i,1)
PERF-UP(i,19,1)
GOSUB(plant)
NEXT
LET(time out := TIME_CHALLENGE)
EXIT
}
CASE(4){
SET(i,3)
PERF-UP(i,5,1)
// laterales medios
[ADD(MUL(ADD(i,9),w),5):ADD(MUL(ADD(i,9),w),8)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,9),w),SUB(w,7)):ADD(MUL(ADD(i,9),w),SUB(w,4))] {BORDER} CPUT(board)
// medio arriba
[ADD(MUL(i,w),17):ADD(MUL(i,w),20)] {BORDER} CPUT(board)
[ADD(MUL(i,w),31):ADD(MUL(i,w),34)] {BORDER} CPUT(board)
// medio abajo
[ADD(MUL(ADD(i,19),w),17):ADD(MUL(ADD(i,19),w),20)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,19),w),31):ADD(MUL(ADD(i,19),w),34)] {BORDER} CPUT(board)
NEXT
EXIT
}
CASE(5){
SET(i,3)
PERF-UP(i,5,1)
// laterales extremos:
[ADD(MUL(i,w),5):ADD(MUL(i,w),8)] {BORDER} CPUT(board)
[ADD(MUL(i,w),SUB(w,7)):ADD(MUL(i,w),SUB(w,4))] {BORDER} CPUT(board)
// laterales medios
[ADD(MUL(ADD(i,9),w),5):ADD(MUL(ADD(i,9),w),8)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,9),w),SUB(w,7)):ADD(MUL(ADD(i,9),w),SUB(w,4))] {BORDER} CPUT(board)
// laterales fondo
[ADD(MUL(ADD(i,19),w),5):ADD(MUL(ADD(i,19),w),8)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,19),w),SUB(w,7)):ADD(MUL(ADD(i,19),w),SUB(w,4))] {BORDER} CPUT(board)
// medio arriba
[ADD(MUL(i,w),17):ADD(MUL(i,w),20)] {BORDER} CPUT(board)
[ADD(MUL(i,w),31):ADD(MUL(i,w),34)] {BORDER} CPUT(board)
// medio abajo
[ADD(MUL(ADD(i,19),w),17):ADD(MUL(ADD(i,19),w),20)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,19),w),31):ADD(MUL(ADD(i,19),w),34)] {BORDER} CPUT(board)
NEXT
EXIT
}
CASE(6){
SET(i,1)
PERF-UP(i,29,1)
GOSUB(plant)
NEXT
LET(time out := TIME_CHALLENGE)
}
CASE(7){
SET(i,3)
PERF-UP(i,5,1)
// laterales extremos:
[ADD(MUL(i,w),5):ADD(MUL(i,w),8)] {BORDER} CPUT(board)
[ADD(MUL(i,w),SUB(w,7)):ADD(MUL(i,w),SUB(w,4))] {BORDER} CPUT(board)
// laterales medios
[ADD(MUL(ADD(i,9),w),5):ADD(MUL(ADD(i,9),w),8)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,9),w),SUB(w,7)):ADD(MUL(ADD(i,9),w),SUB(w,4))] {BORDER} CPUT(board)
// laterales fondo
[ADD(MUL(ADD(i,19),w),5):ADD(MUL(ADD(i,19),w),8)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,19),w),SUB(w,7)):ADD(MUL(ADD(i,19),w),SUB(w,4))] {BORDER} CPUT(board)
// medio arriba
[ADD(MUL(i,w),17):ADD(MUL(i,w),20)] {BORDER} CPUT(board)
[ADD(MUL(i,w),31):ADD(MUL(i,w),34)] {BORDER} CPUT(board)
// medio medio
[ADD(MUL(ADD(i,9),w),17):ADD(MUL(ADD(i,9),w),20)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,9),w),31):ADD(MUL(ADD(i,9),w),34)] {BORDER} CPUT(board)
// medio abajo
[ADD(MUL(ADD(i,19),w),17):ADD(MUL(ADD(i,19),w),20)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,19),w),31):ADD(MUL(ADD(i,19),w),34)] {BORDER} CPUT(board)
NEXT
EXIT
}
SELEND
COND( NOT-EQ?(LEVEL,3) )
LET( nHead := MUL( w, SUB( SUB( h, 1 ), MOD(h,2) )) DIV-INTO(2) )
LET( dir := N )
ELS
LET( nHead := ADD( MUL( w, 13 ), 26 ) )
LET( dir := N )
CEND
[ nHead ] {initial pos} CPUT( board )
IF( swPierde, SRAND( ~SECONDS); FALSE(swPierde); {TIME_LOST} »» (time out),\
SRAND( 26785 ) )
GOSUB( plant )
RET
DEF-FUN( you lost )
SET(i,1), SET(k,0), SET(n,1)
FOR( LE?(i, h), ++i)
SET(j,1)
FOR( LE?(j, w), ++j)
LET( k := [ n ] GET(board) )
COND( IS-NEG?( k ))
LOCATE(i,j)
PRN("\033[38;15;3m\033[48;5;9m~\OFF")
CEND
++n
NEXT
NEXT
SYS("aplay fl/Snake_dolor.wav </dev/null >/dev/null 2>&1 &")
RET
DEF-FUN( show )
SET(i,1)
MSET(j, k)
SET(n,1)
LOCATE(1,1)
FOR( LE?(i, h), ++i)
SET(j,1),LOG-INV(tiles)
FOR( LE?(j, w), ++j)
LET( k := [ n ] GET(board) )
COND( IS-NEG?( k ))
COND( NOT-EQ?(n,nHead) )
IF(GT?(k,-3),IF(tiles,{tile1},{tile2});{"\033[38;5;15m+\OFF"},"\033[38;5;6m\033[48;5;11m \OFF")
ELS
COND( EQ?(dir,N))
IF(tiles,"\033[38;5;9m\033[48;5;15mv\OFF","\033[38;5;9m\033[48;5;15m|\OFF")
ELS-COND( EQ?(dir,S))
IF(tiles,"\033[38;5;9m\033[48;5;15m|\OFF","\033[38;5;9m\033[48;5;15m^\OFF")
ELS-COND( EQ?(dir,E))
IF(tiles,"\033[38;5;9m\033[48;5;15m<\OFF","\033[38;5;9m\033[48;5;15m-\OFF")
ELS
IF(tiles,"\033[38;5;9m\033[48;5;15m-\OFF","\033[38;5;9m\033[48;5;15m>\OFF")
CEND
CEND
ELS-COND( {k} IS-EQ?(BORDER))
{"\033[38;5;82m\033[48;5;57m \OFF"}
ELS-COND( {k}IS-EQ?(FOOD) )
COLOR-FG(color food ),IF(tiles,{tile1},{tile2}) //back food)
{"@\OFF"}
ELS
IF(tiles,{tile1},{tile2})
{" \OFF"}
CEND
PRN
LOG-INV(tiles)
++n
NEXT
PRNL
NEXT
color food+=2,
back food-=2, WHEN( EQ?(color food, PLUS-ONE(BACK_FOOD))){
LET( color food:=COLOR_FOOD)
LET( back food:=BACK_FOOD)
}
RET
// negative values denote the snake (a negated time-to-live in given cell)
// reduce a time-to-live, effectively erasing the tail
DEF-FUN( age )
MSET( r, jAge, jR )
CART( IS-NEG?( board ) ) »» (r), SET-RANGE(r)
GET( board ) PLUS(1) »» (jAge)
// this is necessary, because Hopper arrays begining in 1
CART( IS-ZERO?(jAge) ) »» (jR)
COND( IS-ARRAY?(jR) )
SET-RANGE(jR), SET(jAge, 1), SET-RANGE(r)
CEND
// ******
{jAge} PUT(board), CLR-RANGE
RET
DEF-FUN( step )
LET( len := [nHead] GET(board) )
SELECT(dir)
CASE (N){ nHead -= w, EXIT }
CASE (S){ nHead += w, EXIT }
CASE (W){ --nHead, EXIT }
CASE (E){ ++nHead, EXIT }
SELEND
SELECT( [nHead]CGET(board))
CASE (SPACE){
--len, LET( len := IF( EQ?(len,0), 1, len) )
[nHead] { len }, CPUT(board) // keep in mind len is negative
GOSUB( age )
EXIT
}
CASE (FOOD){
COND( NOT(OR(EQ?(LEVEL,3),EQ?(LEVEL,6))))
--len, LET( len := IF( IS-ZERO?(len), 1, len) )
ELS
++len // quita celda del cuerpo: suma vida!
CEND
[nHead] { len }, CPUT(board)
WHEN(AND(NOT-EQ?(LEVEL,3),NOT-EQ?(LEVEL,6))){ GOSUB( plant ) }
ADD(SCORE,TIME), MOVE-TO(SCORE)
{SCORE, 4, COLUMNA_PRINT} GOSUB( put score )
LET( TIME := 100 )
++counter, COND( EQ?( counter, 25 ) )
LET( TLimit := SUB( TLimit,5 ))
CLAMP(INF_TIME, LIMIT_TIME, TLimit)
LET( counter := 0 )
CEND
WHEN( OR(EQ?(LEVEL,3),EQ?(LEVEL,6))){ ++Consumed stage }
++ConsPrey
COLOR-FG(10)
LOCATE(20,COLUMNA_PRINT) PRN("SPEED: ")
LOC-ROW(21), PRN( ROUND(MUL(INV(TLimit),LIMIT_TIME),2), " M/S " )
LOC-ROW(23) PRN("CONSUMED PREY:")
LOC-ROW(24) PRN(ConsPrey,"\OFF")
LET( color food:=COLOR_FOOD)
LET( back food:=BACK_FOOD)
SYS("aplay fl/Snake_mascada.wav </dev/null >/dev/null 2>&1 &")
TIC( T ),{TIME, 12, COLUMNA_PRINT} GOSUB( put time )
EXIT
}
CASE-NEGATIVE {
GOSUB(KILLSOUND)
SYS("aplay fl/Snake_mascada.wav </dev/null >/dev/null 2>&1")
LET( quit := 1 )
EXIT
}
DEFAULT {
GOSUB(KILLSOUND)
SYS("aplay fl/Snake_golpe.wav </dev/null >/dev/null 2>&1")
LET( quit := 1 )
}
SELEND
RET
DEF-FUN( LEVELCLEAR )
GOSUB(KILLSOUND)
GOSUB( level clear )
SYS("aplay fl/Snake_level_clear.wav </dev/null >/dev/null 2>&1")
SLEEP(1)
WHEN( OR( EQ?(LEVEL,3), EQ?(LEVEL,6))){
WHEN( EQ?(Consumed stage,20)){
GOSUB( show )
SYS( "aplay fl/Snake_bono.wav </dev/null >/dev/null 2>&1 &" )
GOSUB(GANA BONO),SLEEP(3)
ADD(SCORE,1000), MOVE-TO(SCORE)
{SCORE, 4, COLUMNA_PRINT} GOSUB( put score )
}
}
GOSUB( another play )
RET
DEF-FUN( another play )
++sound index, WHEN( EQ?(sound index,6)){ LET(sound index := 2) }
back tiles+=2, WHEN( EQ?(back tiles,15)){ LET(back tiles:=1) }
[back tiles] GET(head back tiles), MOVE-TO(tile1)
[PLUS-ONE(back tiles)] GET(head back tiles), MOVE-TO(tile2)
++LEVEL, WHEN( GT?(LEVEL,7)){ LET(LEVEL:=1) }
++ REAL LEVEL
COLOR-FG(15),LOCATE(26,COLUMNA_PRINT) SET-ITALIC, PRN("LEVEL: ",REAL LEVEL,"\OFF")
LET( len := [nHead] GET(board) )
[1:Size] MEM(1) CPUT(board)
CLR-INTERVAL
MEM(len), GOSUB( start )
GOSUB( show )
COND( OR( EQ?(LEVEL,3), EQ?(LEVEL,6)))
GOSUB(challenge stage)
ELS
GOSUB( ready )
CEND
SLEEP(2)
SYS( CAT(CAT("aplay fl/",[sound index]CGET(sounds)), "</dev/null >/dev/null 2>&1 &"))
LET( TIME := TOTAL_TIME )
{TIME, 12, COLUMNA_PRINT} GOSUB( put time )
RET
// put a piece of food at random empty position
DEF-FUN( plant )
SET(r, 0)
LOOP( search position )
LET( r := MOD( CEIL(RAND(MINUS-ONE(Size))), Size ) )
BACK-IF-NOT-EQ( [r] GET(board), SPACE, search position)
[r] {FOOD} CPUT( board )
RET
DEF-FUN( put titles )
LOCATE(2,COLUMNA_PRINT) PRN("\033[38;5;15mSCORE\OFF")
{SCORE, 4, COLUMNA_PRINT} GOSUB( put score )
LOCATE(10,COLUMNA_PRINT) PRN("\033[38;5;11mTIME\OFF")
{TIME, 12, COLUMNA_PRINT} GOSUB( put time )
COLOR-FG(15)
LOCATE(26,COLUMNA_PRINT) SET-ITALIC, PRN("LEVEL: ",REAL LEVEL,"\OFF")
LOCATE(27,COLUMNA_PRINT) SET-INVERSE, PRN("LIVES: ",lives,"\OFF")
RET
DEF-FUN( put time, B, posx, posy )
MSET( i,j,x )
MSET( sb, lsb,nB, p4 )
SET( k,1 )
LOCATE (posx, posy) FILL-BOX(" ",5,20)
LET( sb := STR(B) )
LET( lsb := LEN(sb) )
SET( rx, posy )
LET( p4 := ADD( posx, 4 ))
COLOR-FG(11)
PERF-UP(k, lsb, 1)
LET( nB := VAL( MID( 1, k, sb )) )
SET(x, 1), SET( i, posx )
FOR( LE?(i, p4), ++i )
SET( j, rx )
FOR( LE?(j, ADD( rx, 2 ) ), ++j )
LOCATE(i, j) PRN( STR-TO-UTF8(CHAR( [ PLUS-ONE(nB), x] CGET(big number) MUL-BY(219) )))
++x
NEXT
NEXT
rx += 4
NEXT
PRN("\OFF")
RET
DEF-FUN( put score, SCORE, posx, posy )
MSET( ln,s, sp )
LET( sp := STR( SCORE ))
LET( ln := LEN(sp))
LOCATE ( posx, posy ) FILL-BOX(" ",4,20)
SET(i, 1)
COLOR-FG(15)
PERF-UP( i, ln, 1)
LET( s := VAL( MID( 1, i, sp )) )
[ PLUS-ONE(s) ]
LOCATE( posx, posy ), PRN ( STR-TO-UTF8( GET(numL1) ))
LOC-ROW( PLUS-ONE(posx) ), PRN ( STR-TO-UTF8( GET(numL2) ))
LOC-ROW( PLUS-TWO(posx) ), PRN ( STR-TO-UTF8( GET(numL3) ))
posy += 2
NEXT
PRN("\OFF")
COND( swExtra1 )
COND( GE?(SCORE,5000) )
++lives
FALSE(swExtra1)
SYS( "aplay fl/Snake_bono.wav </dev/null >/dev/null 2>&1 &" )
LOCATE(27,COLUMNA_PRINT) SET-INVERSE, PRN("LIVES: ",lives,"\OFF")
CEND
ELS-COND(swExtra2)
COND( GE?(SCORE,10000) )
++lives
FALSE(swExtra2)
SYS( "aplay fl/Snake_bono.wav </dev/null >/dev/null 2>&1 &" )
LOCATE(27,COLUMNA_PRINT) SET-INVERSE, PRN("LIVES: ",lives,"\OFF")
CEND
CEND
RET
DEF-FUN( set score )
{"┌┐"," ┐","┌┐","┌┐","┐┐","┌┐","┌┐","┌┐","┌┐","┌┐"} APND-LST(numL1)
{"││"," │","┌┘"," ┤","└┤","└┐","├┐"," │","├┤","└┤"} APND-LST(numL2)
{"└┘"," ┴","└┘","└┘"," ┘","└┘","└┘"," ┴","└┘","└┘"} APND-LST(numL3)
{1,1,1,1,0,1,1,0,1,1,0,1,1,1,1} APND-ROW( big number )
{1,1,0,0,1,0,0,1,0,0,1,0,1,1,1} APND-ROW( big number )
{1,1,1,0,0,1,1,1,1,1,0,0,1,1,1} APND-ROW( big number )
{1,1,1,0,0,1,0,1,1,0,0,1,1,1,1} APND-ROW( big number )
{1,0,1,1,0,1,1,1,1,0,0,1,0,0,1} APND-ROW( big number )
{1,1,1,1,0,0,1,1,1,0,0,1,1,1,1} APND-ROW( big number )
{1,0,0,1,0,0,1,1,1,1,0,1,1,1,1} APND-ROW( big number )
{1,1,1,0,0,1,0,0,1,0,0,1,0,0,1} APND-ROW( big number )
{1,1,1,1,0,1,1,1,1,1,0,1,1,1,1} APND-ROW( big number )
{1,1,1,1,0,1,1,1,1,0,0,1,0,0,1} APND-ROW( big number )
RET
DEF-FUN( ready )
{"\033[38;5;4m\033[48;5;11m"}
LOC-COL(16)
LOC-ROW(13); PRN( STR-TO-UTF8(" ▄ ▄▄ ▄ ▄▄ ▄ ▄ "))
LOC-ROW(14); PRN( STR-TO-UTF8(" █▄▀ █▀ █▄█ █ █ ▀▄▀ "))
LOC-ROW(15); PRN( STR-TO-UTF8(" ▀ ▀ ▀▀ ▀ ▀ ▀▄▀ ▀ "))
PRN("\OFF")
RET
DEF-FUN( level clear )
{"\033[38;5;4m\033[48;5;11m"}
LOC-COL(17)
LOC-ROW(12); PRN( STR-TO-UTF8( " ▄ ▄▄ ▄ ▄ ▄▄ ▄ "))
LOC-ROW(13); PRN( STR-TO-UTF8( " █ █▀ █ █ █▀ █ "))
LOC-ROW(14); PRN( STR-TO-UTF8( " ▀ ▀▀ ▀ ▀▀ ▀ "))
LOC-ROW(15); PRN( STR-TO-UTF8( " ▄ ▄ ▄▄ ▄ ▄ "))
LOC-ROW(16); PRN( STR-TO-UTF8( " █ █ █▀ █▄█ █▄▀ "))
LOC-ROW(17); PRN( STR-TO-UTF8( " ▀ ▀ ▀▀ ▀ ▀ ▀ ▀ "))
PRN("\OFF")
RET
DEF-FUN( challenge stage )
{"\033[38;5;4m\033[48;5;11m"}
LOC-COL(9)
LOC-ROW(12); PRN( STR-TO-UTF8( " ▄ ▄ ▄ ▄ ▄ ▄ ▄▄ ▄ ▄▄ ▄▄ "))
LOC-ROW(13); PRN( STR-TO-UTF8( " █ █▄█ █▄█ █ █ █▀ █ █ █ ▄ █▀ "))
LOC-ROW(14); PRN( STR-TO-UTF8( " ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀▀ ▀ ▀ ▀▀ ▀▀ "))
LOC-ROW(15); PRN( STR-TO-UTF8( " ▄ ▄ ▄ ▄ ▄▄ ▄▄ "))
LOC-ROW(16); PRN( STR-TO-UTF8( " ▀▄ █ █▄█ █ ▄ █▀ "))
LOC-ROW(17); PRN( STR-TO-UTF8( " ▀ ▀ ▀ ▀ ▀▀ ▀▀ "))
PRN("\OFF")
RET
DEF-FUN( GANA BONO )
{"\033[38;5;11m\033[48;5;196m"}
LOC-COL(17)
LOC-ROW(12); PRN( STR-TO-UTF8(" ▄ ▄ ▄ ▄ ▄ ▄ "))
LOC-ROW(13); PRN( STR-TO-UTF8(" █▄▀ █ █ █ █ █ █ ▀▄ "))
LOC-ROW(14); PRN( STR-TO-UTF8(" ▀▄▀ ▀ ▀ ▀ ▀ ▀ "))
LOC-ROW(15); PRN( STR-TO-UTF8(" ▄ ▄ ▄ ▄ "))
LOC-ROW(16); PRN( STR-TO-UTF8(" █ █ █ █ █ █ █ "))
LOC-ROW(17); PRN( STR-TO-UTF8(" █ ▄ ▀▄▀ ▀▄▀ ▀▄▀ "))
PRN("\OFF")
RET
DEF-FUN( game over )
{"\033[38;5;15m\033[48;5;9m"}
LOC-COL(17)
LOC-ROW(12); PRN( STR-TO-UTF8(" ▄▄ ▄ ▄ ▄ ▄▄ "))
LOC-ROW(13); PRN( STR-TO-UTF8(" █ ▄ █▄█ █ █ █ █▀ "))
LOC-ROW(14); PRN( STR-TO-UTF8(" ▀▀ ▀ ▀ ▀ ▀ ▀ ▀▀ "))
LOC-ROW(15); PRN( STR-TO-UTF8(" ▄ ▄ ▄ ▄▄ ▄ "))
LOC-ROW(16); PRN( STR-TO-UTF8(" █ █ █ █ █▀ █▄▀ "))
LOC-ROW(17); PRN( STR-TO-UTF8(" ▀ ▀ ▀▀ ▀ ▀ "))
PRN("\OFF")
RET
DEF-FUN( titles )
#define COLOR_INI 232
#define COLOR_FIN 255
SET(k,1)
PERF-UP(k,2,1)
SET(j,COLOR_INI), SET(jbg,COLOR_FIN)
PERF-UP(j,COLOR_FIN,1)
COLOR(j, jbg--)
LOC-COL(17)
LOC-ROW(12); PRN(STR-TO-UTF8(" "))
LOC-ROW(13); PRN(STR-TO-UTF8(" ▄ ▄ ▄ ▄ ▄ ▄▄ "))
LOC-ROW(14); PRN(STR-TO-UTF8(" ▀▄ █ █ █▄█ █▄ █▀ "))
LOC-ROW(15); PRN(STR-TO-UTF8(" ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀▀ "))
LOC-ROW(16); PRN(STR-TO-UTF8(" ▄ ▄ ▄▄ ▄▄ ▄▄ "))
LOC-ROW(17); PRN(STR-TO-UTF8(" ▀▄ █▄█ █▀ █▀ █ █ "))
LOC-ROW(18); PRN(STR-TO-UTF8(" ▀ ▀ ▀▀ ▀▀ ▀▄▀ "))
LOC-ROW(19); PRN(STR-TO-UTF8(" "))
MICROSECS(20000)
NEXT
SET(j,COLOR_FIN), SET(jbg,COLOR_INI)
PERF-DOWN(j,COLOR_INI,1)
COLOR(j, jbg++)
LOC-COL(17)
LOC-ROW(12); PRN(STR-TO-UTF8(" "))
LOC-ROW(13); PRN(STR-TO-UTF8(" ▄ ▄ ▄ ▄ ▄ ▄▄ "))
LOC-ROW(14); PRN(STR-TO-UTF8(" ▀▄ █ █ █▄█ █▄ █▀ "))
LOC-ROW(15); PRN(STR-TO-UTF8(" ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀▀ "))
LOC-ROW(16); PRN(STR-TO-UTF8(" ▄ ▄ ▄▄ ▄▄ ▄▄ "))
LOC-ROW(17); PRN(STR-TO-UTF8(" ▀▄ █▄█ █▀ █▀ █ █ "))
LOC-ROW(18); PRN(STR-TO-UTF8(" ▀ ▀ ▀▀ ▀▀ ▀▄▀ "))
LOC-ROW(19); PRN(STR-TO-UTF8(" "))
MICROSECS(20000)
NEXT
NEXT
PRN("\OFF")
RET
DEF-FUN( prepare maze )
REPL(12,ADD(MUL(w, 3),20),"333333333333",Maze), MOVE-TO(Maze)
REPL(12,ADD(MUL(w, 24),20),"333333333333",Maze), MOVE-TO(Maze)
REPL(12,ADD(MUL(w, 6),7),"333333333333",Maze), MOVE-TO(Maze)
REPL(12,ADD(MUL(w, 6),33),"333333333333",Maze), MOVE-TO(Maze)
REPL(12,ADD(MUL(w, 21),7),"333333333333",Maze), MOVE-TO(Maze)
REPL(12,ADD(MUL(w, 21),33),"333333333333",Maze), MOVE-TO(Maze)
SET(i,7)
PERF-UP(i,10,1)
REPL(1,ADD(MUL(w, i),7),"3",Maze), MOVE-TO(Maze)
REPL(1,ADD(MUL(w, i),44),"3",Maze), MOVE-TO(Maze)
REPL(1,ADD(MUL(w, ADD(i,10)),44),"3",Maze), MOVE-TO(Maze)
REPL(1,ADD(MUL(w, ADD(i,10)),7),"3",Maze), MOVE-TO(Maze)
NEXT
RET
</syntaxhighlight>
{{out}}
[[File:Captura_de_pantalla_de_2022-10-07_14-13-27.png]]
=={{header|AutoHotkey}}==
<
Gui, +AlwaysOnTop
Gui, font, s12, consolas
Line 128 ⟶ 1,151:
return [oGrid, row, col]
}
;-----------------------------------------</
=={{header|BASIC}}==
==={{header|Applesoft BASIC}}===
Rather than using DIM X%(1599),Y%(1599) which takes a long time to initialize, instead to speed this up, the program will POKE and PEEK the snake coordinates in memory. On initialization, LOMEM: 24576 moves variables out of the way.
Continue playing by typing the CONT command from the Applesoft BASIC prompt if you quit or it's game over.
<syntaxhighlight lang="basic">REM === VARIABLES ===
REM
REM D(4) X DELTAS
REM E(4) Y DELTAS
REM K(255) KEYCODE BEHESTS
REM FN X X COORDINATES OF SNAKE FROM MEMORY
REM FN Y Y COORDINATES OF SNAKE FROM MEMORY
REM A <UNUSED>
REM B BEHEST: 0=NOTHING 1=RIGHT 2=LEFT 3=DOWN 4=UP 5=QUIT
REM C SCRN COLOR
REM D X-DELTA
REM E Y-DELTA
REM F FOOD COLOR= 4 DARK GREEN
REM H HIT= 127
REM I KEYBOARD= 49152
REM J CLEAR KEYBOARD= 49168
REM K KEYCODE
REM L LENGTH WIDTH&HEIGHT= 40
REM M MAX= 1599 UPPER BOUND OF SNAKE X AND Y COORDINATES
REM N NEW POSITION OF HEAD OF SNAKE
REM O OLD POSITION OF TAIL OF SNAKE
REM P ADDRESS OF 1600 X COORDINATES OF SNAKE
REM Q QUIT= 5
REM R REMAINING COLOR= 9 ORANGE
REM S SNAKE HEAD COLOR= 13 YELLOW
REM T ONE= 1
REM U X FOOD COORDINATE
REM V Y FOOD COORDINATE
REM W WALL COLOR= 10 GREY
REM X X SNAKE HEAD COORDINATE
REM Y Y SNAKE HEAD COORDINATE
REM Z ADDRESS OF 1600 Y COORDINATES OF SNAKE
REM Q$ QUIT MESSAGE
REM === KEYBOARD ===
REM
REM UPPER LOWER KEY BEHEST
REM 155 ESC QUIT
REM 139 UP UP
REM 193 225 A UP
REM 201 233 I UP
REM 138 DOWN DOWN
REM 218 250 Z DOWN
REM 203 235 K DOWN
REM 136 LEFT LEFT
REM 202 234 J LEFT
REM 149 RIGHT RIGHT
REM 204 236 L RIGHT
0 ON B = Q GOTO 5: IF B THEN D = D(B):E = E(B)
1 X = X + D:Y = Y + E:C = SCRN( X,Y): COLOR= S: PLOT X,Y: COLOR= R: PLOT FN X(N), FN Y(N):N = N - T + (N = 0) * M: POKE P + N,X: POKE Z + N,Y: ON C(C) GOTO 4: IF NOT C THEN COLOR= 0: PLOT FN X(O), FN Y(O):O = O - T + (O = 0) * M
2 IF C THEN U = INT ( RND (T) * L):V = INT ( RND (T) * L): ON SCRN( U,V) > 0 GOTO 2: COLOR= F: PLOT U,V
3 K = PEEK (I):B = PEEK (J * (K > H)):B = K(K): GOTO
4 COLOR= T: PLOT X,Y: READ Q$: DATA5,20,20,1,-1,1,-1,13,9,4,10,1,40,1599,127,49152,49168,4608,6400,5,4,4,4,4,4,3,3,3,3,3,2,2,2,1,1,1,DONE,GAME OVER
5 IF Q = 5 THEN PRINT Q$;: END : HOME :Q = Q * (B = Q):B = 0: ON Q = 5 GOTO : RUN
6 LOMEM: 24576
7 DIM K(255),D(4),E(4),C(15): READ Q,X,Y,D(1),D(2),E(3),E(4),S,R,F,W,T,L,M,H,I,J,P,Z,K(155),K(139),K(193),K(225),K(201),K(233),K(138),K(218),K(250),K(203),K(235),K(136),K(202),K(234),K(149),K(204),K(236),Q$
8 DEF FN X(I) = PEEK (P + I): DEF FN Y(I) = PEEK (Z + I):B = RND (0):C(S) = T:C(R) = T:C(W) = T:B = INT ( RND (T) * 4) + T:D = D(B):E = E(B): POKE P + O,X: POKE Z + O,Y
9 GR : HOME : COLOR= W: VLIN 0,39 AT 0: VLIN 0,39 AT 39: HLIN 1,38 AT 0: HLIN 1,38 AT 39: COLOR= F: PLOT X + D,Y + E: GOTO</syntaxhighlight>
==={{header|Craft Basic}}===
[[File:Craft Basic Snake.png|right]]
<syntaxhighlight lang="basic">rem Snake example game for Craft Basic
rem by Gemino Smothers 2022
rem www.lucidapogee.com
title "Snake!"
define gfxx = 330, gfxy = 296
define upkey = 0, rightkey = 0, downkey = 0, leftkey = 0, esckey = 0
define speed = 0, delay = 0, score = 0, game = 1
define maxsize = 1000, size = 9, direction = int(rnd * 4) + 1
define rx = int(rnd * (gfxx - 24)) + 12
define ry = int(rnd * (gfxy - 40)) + 25
dim sx[maxsize]
dim sy[maxsize]
let sx[0] = gfxx / 2
let sy[0] = gfxy / 2
fill on
bgcolor 128, 64, 0
cls graphics
resize 0, 0, gfxx + 10, gfxy + 56
center
formid 1
staticform 1, 1, 100, 14
fgcolor 255, 255, 0
bgcolor 0, 80, 0
colorform
alert "Snake! by Gemino Smothers 2022"
alert "Get the gray little rodents and avoid hitting yourself or walls."
alert "Use arrow keys to move. Esc to exit."
input "Enter game speed between 0 to 100+", speed
fgcolor 0, 80, 0
rect 0, 0, gfxx, gfxy
do
button upkey, 38
button rightkey, 39
button downkey, 40
button leftkey, 37
button esckey, 27
if upkey = 1 and direction <> 3 then
let direction = 1
endif
if rightkey = 1 and direction <> 4 then
let direction = 2
endif
if downkey = 1 and direction <> 1 then
let direction = 3
endif
if leftkey = 1 and direction <> 2 then
let direction = 4
endif
fgcolor 0, 80, 0
oval sx[size], sy[size], 15, 15
let i = size + 1
do
let i = i - 1
let c = i - 1
if sx[0] = sx[i] and sy[0] = sy[i] = 1 then
let game = 0
endif
let sx[i] = sx[c]
let sy[i] = sy[c]
fgcolor 0, 255, 0
oval sx[i], sy[i], 15, 15
loop i > 1
fgcolor 0, 0, 255
oval sx[0] + 5, sy[0] + 5, 3, 3
oval sx[0] + 9, sy[0] + 5, 3, 3
if direction = 1 then
let sy[0] = sy[0] - 15
endif
if direction = 2 then
let sx[0] = sx[0] + 15
endif
if direction = 3 then
let sy[0] = sy[0] + 15
endif
if direction = 4 then
let sx[0] = sx[0] - 15
endif
if sx[0] <= -10 or sx[0] >= gfxx or sy[0] <= -10 or sy[0] >= gfxy = 1 then
let game = 0
endif
if sx[0] + 15 >= rx and sx[0] <= rx + 15 and sy[0] + 15 >= ry and sy[0] <= ry + 15 = 1 then
playwave "examples\tada.wav"
fgcolor 0, 80, 0
rect 0, 0, gfxx, gfxy
let rx = int(rnd * (gfxx - 24)) + 12
let ry = int(rnd * (gfxy - 40)) + 25
let size = size + 3
let score = score + 1
endif
fgcolor 100,100,100
oval rx, ry, 15, 15
fgcolor 255, 0, 0
oval rx + 5, ry + 5, 3, 3
oval rx + 9, ry + 5, 3, 3
fgcolor 255, 255, 0
formid 1
formtext "Score: ", score
updateform
let delay = clock
do
wait
loop clock < delay + speed
loop esckey <> 1 and game = 1
playwave "examples\boom.wav"
alert "Game over! Score: ", score</syntaxhighlight>
==={{header|FreeBASIC}}===
<
REM Snake
Line 249 ⟶ 1,508:
End
'--------------------------
</syntaxhighlight>
==={{header|Integer BASIC}}===
{{Trans|Applesoft BASIC}}
<syntaxhighlight lang="basic"> 0 IF B=Q THEN GOTO 9: IF B THEN D=D(B): IF B THEN E=E(B):X=X+D:Y=Y+E:A= SCRN(X,Y): COLOR=S: PLOT X,Y: COLOR=R: PLOT X(N),Y(N)
1 N=N-T+(N=1)*M:X(N)=X:Y(N)=Y: IF C(A) THEN GOTO 9: IF A THEN GOTO 2: COLOR=0: PLOT X(O),Y(O):O=O-T+(O=1)*M: GOTO 3
2 U= RND (L):V= RND (L): IF SCRN(U,V)>0 THEN GOTO 2: COLOR=F: PLOT U,V
3 K= PEEK (I):B= PEEK (J*(K>H)):B=K(K): GOTO 0
4 DIM X(1600),Y(1600),K(255),D(4),E(4),C(15): FOR I=0 TO 255:K(I)=0: NEXT I: FOR I=1 TO 4:D(I)=0:E(I)=0: NEXT I
5 FOR I=0 TO 15:C(I)=0: NEXT I:Q=5:X=20:Y=20:E(1)=-1:D(2)=-1:E(3)=1:D(4)=1:S=13:R=9:F=4:W=10:T=1:L=40:M=1600
6 H=127:I=-16384:J=-16368:K(155)=5:K(193)=1:K(225)=1:K(201)=1:K(233)=1:K(136)=2:K(202)=2:K(234)=2:N=1:O=1
7 K(218)=3:K(250)=3:K(203)=3:K(235)=3:K(149)=4:K(204)=4:K(236)=4:C(S)=T:C(R)=T:C(W)=T:B= RND (4)+T:D=D(B)
8 E=E(B):X(O)=X:Y(O)=Y: GR : CALL -936: COLOR=W: VLIN 0,39 AT 0: VLIN 0,39 AT 39: HLIN 1,38 AT 0: HLIN 1,38 AT 39: COLOR=F: PLOT X+D,Y+E: GOTO 0
9 IF Q#5 THEN GOTO 4:Q=Q*(B=Q): COLOR=T: IF Q=0 THEN PLOT X,Y: IF Q=0 THEN PRINT "GAME OVER";: IF Q THEN PRINT "DONE";:K= PEEK (J):B=0: END </syntaxhighlight>
==={{header|Locomotive Basic}}===
[[File:Snake locomotive basic.png|thumb|''Snake'' running in CPCBasic]]
Use the arrow keys to control movement direction. If you find gameplay too easy, lower the skill parameter in line 20 or increase ml (maximum snake length).
If you would like to play this in [https://benchmarko.github.io/CPCBasic/cpcbasic.html CPCBasic], paste the code into the box at the top in CPCBasic, then click on the "Reset" and "Run" buttons below and finally click on the CPC screen so it gets keyboard focus. (This will turn the frame around the screen dark).
<syntaxhighlight lang="locobasic">10 mode 1:randomize time
15 ink 0,0:ink 1,6:ink 2,18:ink 3,11
20 sx=20:sy=5:dx=1:dy=0:ml=20:dim ox(ml),oy(ml):oi=1:ll=4:skill=8
30 f$=chr$(228):w$=chr$(127):b$=chr$(231)
40 pen 3:print string$(40,w$);
50 for i=2 to 20:print w$;space$(38);w$;:next
60 print string$(40,w$);
70 locate
80 gosub 260
140 locate sx,sy:pen 2: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
200 locate 1,24:print "SCORE:"p
210 for i=1 to skill:frame
211 if inkey(1)>-1 then dx=1:dy=0
212 if inkey(8)>-1 then dx=-1:dy=0
213 if inkey(0)>-1 then dx=0:dy=-1
214 if inkey(2)>-1 then dx=0:dy=1
215 next
220 locate sx,sy:pen 2: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
260 fx=rnd*39+1:fy=rnd*19+1
270 locate fx,fy:a$=copychr$(#0)
280 if a$<>" " then 260
290 pen 1:print f$;
300 return</
==={{header|ZX Spectrum Basic}}===
Line 293 ⟶ 1,565:
Note that lines <code>10</code> to <code>210</code> and <code>580</code> to <code>890</code>—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 <code>220</code> to <code>570</code> to work on their own. The things the snake eats are supposed to be apples, although they don't look too much like them.
<
20 READ bits
30 POKE USR "L"+i,bits
Line 381 ⟶ 1,653:
870 DATA BIN 11111100
880 DATA BIN 01111111
890 DATA BIN 00110110</
=={{header|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.
<
// The problem with implementing this task in C is, the language standard
Line 527 ⟶ 1,799:
close_screen();
return 0;
}</
=={{header|C++}}==
Simple Windows console implementation.
[[File:SnakeCpp.png|200px|thumb|right]]
<
#include <windows.h>
#include <ctime>
Line 645 ⟶ 1,917:
snake s; s.play(); return 0;
}
</syntaxhighlight>
=={{header|Delphi}}==
{{libheader| Winapi.Windows}}
Line 656 ⟶ 1,929:
{{libheader| Vcl.ExtCtrls}}
{{Trans|JavaScript}}
<syntaxhighlight lang="delphi">
unit SnakeGame;
Line 1,012 ⟶ 2,285:
FrameTimer.Enabled := True;
end;
end.</
Form resources:
<syntaxhighlight lang="delphi">
object SnakeApp: TSnakeApp
OnCreate = FormCreate
end
</syntaxhighlight>
=={{header|EasyLang}}==
[https://easylang.dev/apps/snake.html Run it]
{{Trans|Craft Basic}}
<syntaxhighlight>
subr fruit
rx = (randint 20 - 1) * 5 + 2.5
ry = (randint 20 - 1) * 5 + 2.5
.
subr start
fruit
game = 1
sx[] = [ 52.5 0 0 0 0 ]
sy[] = [ 52.5 0 0 0 0 ]
dir = randint 4
timer 0
.
background 242
move 30 70
clear
color 997
text "SNAKE"
textsize 5
move 6 40
text "Keys or mouse for controlling"
move 6 30
text "Space or click to to start"
#
on key
if game = 0 and keybkey = " "
start
return
.
if dir mod 2 = 1
if keybkey = "ArrowRight"
dir = 2
elif keybkey = "ArrowLeft"
dir = 4
.
else
if keybkey = "ArrowUp"
dir = 1
elif keybkey = "ArrowDown"
dir = 3
.
.
.
on mouse_down
if game = 0
start
return
.
if dir mod 2 = 1
if mouse_x < sx
dir = 4
else
dir = 2
.
else
if mouse_y < sy
dir = 3
else
dir = 1
.
.
.
on timer
clear
color 997
move 2 95
text "Score: " & 10 * len sx[] - 50
color 966
move rx ry
circle 1.5
#
sx = sx[1] ; sy = sy[1]
if dir = 1
sy += 5
elif dir = 2
sx += 5
elif dir = 3
sy -= 5
elif dir = 4
sx -= 5
.
if sx < 0 or sx > 100 or sy < 0 or sy > 100
game = 0
.
color 494
for i = len sx[] downto 2
if sx = sx[i] and sy = sy[i]
game = 0
.
sx[i] = sx[i - 1]
sy[i] = sy[i - 1]
if sx[i] > 0
move sx[i] sy[i]
circle 2.5
.
.
move sx sy
circle 2.5
color 000
if dir = 2 or dir = 4
move sx sy + 1
circle 0.5
move sx sy - 1
circle 0.5
else
move sx + 1 sy
circle 0.5
move sx - 1 sy
circle 0.5
.
if sx = rx and sy = ry
len sx[] len sx[] + 3
len sy[] len sy[] + 3
fruit
.
sx[1] = sx ; sy[1] = sy
if game = 1
timer 0.15
else
color 997
move 10 10
text "Space or click new game"
.
.
</syntaxhighlight>
=={{header|Emacs Lisp}}==
[[File:Snake emacs lisp.png|thumb|''Snake'' in Emacs]]
GNU Emacs has a built-in Snake game in Lisp that is started with
<syntaxhighlight>M-x snake</syntaxhighlight>
and which uses the same tile set as the included [[Tetris]] game.
=={{header|F Sharp}}==
<syntaxhighlight lang="fsharp">
open System
open System.Threading.Tasks
open System.Threading
module SnakeGame =
/// 🚏 Directions on our grid:
type Movement =
| Left of Position
| Right of Position
| Down of Position
| Up of Position
| InvalidMove
/// 🐍 Sort of like a list, but not:
and Snake<'Position> =
| Head of Position * Snake<'Position>
| Belly of Position * Snake<'Position>
| Tail
/// 🕹️ Our basic runtime information:
and Game =
| GameRunning of Movement * Snake<Position> * Grid * Eaten:int * Food
| GameOver of Grid * Eaten:int
/// 🧭 x & y in our plane:
and Position = int * int
/// 🍎 The food our snake will eat:
and Food = Position * string
/// 🌐 A simple two dimensional plane:
and Grid = string[][]
/// Making a list of positions from a Snake 🐍
let snakeUnzip (snake:Snake<Position>) =
let rec unzip snake carry =
match snake with
| Head (p, rest) -> unzip rest <| carry @ [p]
| Belly (p, rest) -> unzip rest <| carry @ [p]
| Tail -> carry
unzip snake []
/// Making a Snake from a list of positions 🐍
let snakeZip (positions:list<Position>) (upto:int) =
let correctLength = (List.take upto positions)
let rec zip (carry:Snake<Position>) (rest:list<Position>) =
match rest with
| head::[] -> zip (Head(head, carry)) []
| back::front -> zip (Belly (back, carry)) front
| [] -> carry
zip Tail (List.rev correctLength)
module Graphics =
let private random = new Random()
let private head = "🤢"
let private belly = "🟢"
let private display = "⬜"
let private errorDisplay = "🟥"
let private food = [|"🐁";"🐀";"🐥";"🪺";"🐸";"🐛";"🪰";"🐞";"🦗"|]
let private randomFood () = food.[random.Next(food.Length - 1)]
let isFood square = Array.contains square food
let isFreeSpace square = square = display || square = errorDisplay
let isOccupied square =
match square with
| square when isFreeSpace square -> false
| square when isFood square -> false
| _ -> true
let makeGrid (dimensionsSquared) : Grid =
let row _ = Array.init dimensionsSquared (fun _ -> display)
Array.init dimensionsSquared row
let clearGrid (grid:Grid) : unit =
Array.iteri (fun i row ->
Array.iteri (fun j _ ->
grid.[i].[j] <- display
) row
) grid
let render (grid:Grid) : unit =
Console.Clear()
Array.iter (fun (row:string array) ->
let prettyprint = String.concat "" row
printfn $"{prettyprint}") grid
printfn "Snake Game in FSharp by @wiredsister"
printfn "Controls: ⬅️ ↕️ ➡️"
printfn "Press Ctrl+C to Quit Game"
Console.Title <- "FSharp Snake 🐍"
let getFreeSpaces (grid:Grid) : list<Position> =
let results : Position list ref = ref []
for i in 0..(grid.Length-1) do
for j in 0..(grid.Length-1) do
if isFreeSpace grid.[i].[j]
then results.Value <- results.Value @ [i,j]
else ()
()
results.Value
let getFood (grid:Grid) : Food =
Console.Beep()
let freeSpaces =
getFreeSpaces grid
|> Array.ofList
let food = randomFood ()
let randomPos = freeSpaces.[random.Next(freeSpaces.Length - 1)]
randomPos, food
let dropFood (grid:Grid) (food:Food) =
let (x, y), animal = food
grid.[x].[y] <- animal
let slither (snake:Snake<Position>) (grid:Grid) : unit =
try
let rec slithering (body:Snake<Position>) =
match body with
| Head(p, s) ->
let row, column = p
grid.[row].[column] <- head
slithering s
| Belly(p, s) ->
let row, column = p
grid.[row].[column] <- belly
slithering s
| Tail -> ()
do slithering snake
with _ -> failwith "ERROR: Could not slither snake!"
let endGame (grid:Grid) : unit =
Console.Clear()
Array.iteri (fun i row ->
Array.iteri (fun j _ ->
grid.[i].[j] <- errorDisplay
) row
) grid
Array.iter (fun (row:string array) ->
let prettyprint = String.concat "" row
printfn $"{prettyprint}") grid
Console.Beep()
module GamePlay =
let moveUp (snake:Snake<Position>) (grid:Grid) (eaten:int) (food:Food) : Game =
match snake with
| Head (p, rest:Snake<Position>) ->
let x, y = p
let shiftUp = ((x-1), y)
try
match shiftUp with
| (row,column) when Graphics.isOccupied grid.[row].[column] ->
GameOver (grid, eaten)
| (row, column) when Graphics.isFood grid.[row].[column] ->
let unzipped = snakeUnzip (Head (shiftUp, (Belly (p, rest))))
let newSnake = snakeZip unzipped (eaten+1)
let nextFood = Graphics.getFood grid
GameRunning (Up shiftUp, newSnake, grid, eaten+1, nextFood)
| pivot ->
let unzipped = snakeUnzip (Head (pivot, (Belly (p, rest))))
let newSnake = snakeZip unzipped eaten
GameRunning (Up pivot, newSnake, grid, eaten, food)
with _ -> GameOver (grid, eaten)
| _ -> failwith "ERROR: No head!"
let moveDown (snake:Snake<Position>) (grid:Grid) (eaten:int) (food:Food) : Game =
match snake with
| Head (p, rest:Snake<Position>) ->
let x, y = p
let shiftDown = ((x+1), y)
try
match shiftDown with
| (row,column) when Graphics.isOccupied grid.[row].[column] ->
GameOver (grid, eaten)
| (row, column) when Graphics.isFood grid.[row].[column] ->
let unzipped = snakeUnzip (Head (shiftDown, (Belly (p, rest))))
let newSnake = snakeZip unzipped (eaten+1)
let nextFood = Graphics.getFood grid
GameRunning (Down shiftDown, newSnake, grid, (eaten+1), nextFood)
| pivot ->
let unzipped = snakeUnzip (Head (pivot, (Belly (p, rest))))
let newSnake = snakeZip unzipped eaten
GameRunning (Down pivot, newSnake, grid, eaten, food)
with _ -> GameOver (grid, eaten)
| _ -> failwith "ERROR: No head!"
let moveLeft (snake:Snake<Position>) (grid:Grid) (eaten:int) (food:Food) : Game =
match snake with
| Head (p, rest:Snake<Position>) ->
let x, y = p
let shiftLeft = (x, (y-1))
try
match shiftLeft with
| (row,column) when Graphics.isOccupied grid.[row].[column] ->
GameOver (grid, eaten)
| (row, column) when Graphics.isFood grid.[row].[column] ->
let unzipped = snakeUnzip (Head (shiftLeft, (Belly (p, rest))))
let newSnake = snakeZip unzipped (eaten+1)
let nextFood = Graphics.getFood grid
GameRunning (Left shiftLeft, newSnake, grid, eaten+1, nextFood)
| pivot ->
let unzipped = snakeUnzip (Head (pivot, (Belly (p, rest))))
let newSnake = snakeZip unzipped eaten
GameRunning (Left pivot, newSnake, grid, eaten, food)
with _ -> GameOver (grid, eaten)
| _ -> failwith "ERROR: No head!"
let moveRight (snake:Snake<Position>) (grid:Grid) (eaten:int) (food:Food) : Game =
match snake with
| Head (p, rest:Snake<Position>) ->
let (x: int), y = p
let shiftRight = (x, (y+1))
try
match shiftRight with
| (row,column) when Graphics.isOccupied grid.[row].[column] ->
GameOver (grid, eaten)
| (row, column) when Graphics.isFood grid.[row].[column] ->
let unzipped = snakeUnzip (Head (shiftRight, (Belly (p, rest))))
let newSnake = snakeZip unzipped (eaten+1)
let nextFood = Graphics.getFood grid
GameRunning (Right shiftRight, newSnake, grid, eaten+1, nextFood)
| pivot ->
let unzipped = snakeUnzip (Head (pivot, (Belly (p, rest))))
let newSnake = snakeZip unzipped eaten
GameRunning (Right pivot, newSnake, grid, eaten, food)
with _ -> GameOver (grid, eaten)
| _ -> failwith "ERROR: No head!"
open SnakeGame
[<EntryPoint>]
let main _ =
/// A gentle slope function for making the snake go faster:
let tick (eaten:int) = 100./log10(float eaten) |> int
let getNextMove prev snake grid eaten food : Task<Game> =
task {
do! Task.Delay(tick(eaten))
if not Console.KeyAvailable
then
match prev with
| Up _ -> return GamePlay.moveUp snake grid eaten food
| Down _ -> return GamePlay.moveDown snake grid eaten food
| Right _ -> return GamePlay.moveRight snake grid eaten food
| Left _ -> return GamePlay.moveLeft snake grid eaten food
| InvalidMove -> return GameOver (grid, eaten)
else
match Console.ReadKey() with
| keystroke when keystroke.Key.Equals(ConsoleKey.UpArrow) ->
return GamePlay.moveUp snake grid eaten food
| keystroke when keystroke.Key.Equals(ConsoleKey.DownArrow) ->
return GamePlay.moveDown snake grid eaten food
| keystroke when keystroke.Key.Equals(ConsoleKey.RightArrow) ->
return GamePlay.moveRight snake grid eaten food
| keystroke when keystroke.Key.Equals(ConsoleKey.LeftArrow) ->
return GamePlay.moveLeft snake grid eaten food
| _ ->
match prev with
| Up _ -> return GamePlay.moveUp snake grid eaten food
| Down _ -> return GamePlay.moveDown snake grid eaten food
| Right _ -> return GamePlay.moveRight snake grid eaten food
| Left _ -> return GamePlay.moveLeft snake grid eaten food
| InvalidMove -> return GameOver (grid, eaten)
}
let gridDimension = 20
let segments = [(0,3); (0,2); (0,1); (0,0)]
let youngSnake : Snake<Position> = snakeZip segments segments.Length
let startingGrid = Graphics.makeGrid gridDimension
let startingFood = Graphics.getFood startingGrid
let start = GamePlay.moveRight youngSnake startingGrid segments.Length startingFood
let rec gameLoop (game:Game) =
match game with
| GameRunning (prev, snake, grid, eaten, food) ->
do Graphics.clearGrid grid
do Graphics.dropFood grid food
do Graphics.slither snake grid
do Graphics.render grid
let eitherPlayerOrCursor = getNextMove prev snake grid eaten food
do eitherPlayerOrCursor.Wait()
gameLoop eitherPlayerOrCursor.Result
| GameOver (grid,eaten) ->
do Graphics.endGame grid
printfn $"Game Over! Snake ate {eaten-segments.Length} critters!"
do Thread.Sleep(1000)
let rec wantToPlayAgain () =
match Console.ReadKey() with
| keystroke when keystroke.Key.Equals(ConsoleKey.Y) -> gameLoop start
| keystroke when keystroke.Key.Equals(ConsoleKey.N) -> ()
| _ -> wantToPlayAgain ()
printfn $"Restart? Type Y to continue..."
wantToPlayAgain()
do gameLoop start
0
</syntaxhighlight>
=={{header|Go}}==
Line 1,026 ⟶ 2,739:
FreeBSD, OpenBSD, NetBSD, DragonFly BSD, Linux, MS Windows, and MacOS
(tested on FreeBSD and MS Windows).
<
import (
Line 1,260 ⟶ 2,973:
snakeHead = termbox.Cell{Ch: 'O', Bg: bg, Fg: termbox.ColorYellow | termbox.AttrBold}
food = termbox.Cell{Ch: '@', Bg: bg, Fg: termbox.ColorRed}
)</
=={{header|Haskell}}==
<
import Control.Monad.Random (getRandomRs)
import Graphics.Gloss.Interface.Pure.Game
Line 1,362 ⟶ 3,075:
main = do world <- createWorld
play inW white 7 world renderWorld handleEvents updateWorld
where inW = InWindow "The Snake" (400, 400) (10, 10)</
'''Extra credit'''
Line 1,368 ⟶ 3,081:
It is easy to make snake to seek food automatically. Just change the first line of the <code>updateWorld</code> definition:
<
and add local definition:
<
where
optimalDirection = minimumBy (comparing distanceToFood) safeTurns
Line 1,384 ⟶ 3,097:
distanceToFood d = let (a,b) = w^.snake & turns d & moves & (^.body) & head
(x,y) = w^.food & head
in (a-x)^2 + (b-y)^2</
=={{header|J}}==
Needs j9 qt:
Use WASD to move. Use <code>start''</code> to start.
Difficulty may be adjusted by providing a non-default argument to start (time in milliseconds between animation updates). For example, <code>start 200</code> for an easier game.
In this implementation, speed is fixed (and final score is by the update rate). Another approach might be to start slow and gradually speed based on the length of the snake. Those sorts of changes are left as an exercise for the reader.
<syntaxhighlight lang="j">require'ide/qt/gl2'
coinsert 'jgl2'
open=: wd@{{)n
pc s closeok;
cc n isidraw;
set n wh 400 400;
pshow;
}}
start=: {{
speed=: {.y,200
open''
snake=: 10 10,10 11,:10 12
newdot''
draw''
}}
newdot=: {{
dot=: ({~ ?@#) snake -.~ ,/(#: i.)40 40
}}
s_n_char=: {{
select. {.toupper sysdata
case. 'W' do. move 0 _1
case. 'A' do. move _1 0
case. 'S' do. move 0 1
case. 'D' do. move 1 0
end.
}}
move=: {{
s_timer=: move@y
wd 'ptimer ',":speed
head=. y+{.snake
if. head e. snake do. head gameover'' return. end.
if. _1 e. head do. (0>.head) gameover'' return. end.
if. 1 e. 40 <: head do. (39<.head) gameover'' return. end.
if. dot -: head do.
snake=: dot,snake
newdot''
else.
snake=: head,}: snake
end.
draw''
}}
draw=: {{
glclear''
glbrush glrgb 0 0 255
glrect 10*}.snake,"1(1 1)
glbrush glrgb 0 255 0
glrect 10*({.snake),1 1
glbrush glrgb 255 0 0
glrect 10*dot,1 1
glpaint''
EMPTY
}}
gameover=: {{
wd 'ptimer 0'
if. 1<#snake do.
echo 'game over'
echo 'score: ',":(#snake)*1000%speed
draw''
glbrush glrgb 255 255 0
glrect 10*x,1 1
end.
snake=: ,:_ _
}}</syntaxhighlight>
=={{header|Java}}==
Line 1,392 ⟶ 3,186:
=={{header|JavaScript}}==
You need the P5 Library to run this code!
<
const L = 1, R = 2, D = 4, U = 8;
var block = 24, wid = 30, hei = 20, frameR = 7, fruit, snake;
Line 1,500 ⟶ 3,294:
if( !snake.alive ) text( "THE END", 630, 250 );
}
</syntaxhighlight>
=={{header|Julia}}==
<syntaxhighlight lang="julia">using GLMakie, GeometryBasics
mutable struct SnakeGame
height::Int
width::Int
snake::Vector{CartesianIndex{2}}
food::CartesianIndex{2}
end
Line 1,549 ⟶ 3,342:
function play(;n=10,t=0.5)
game =
scene = Scene(resolution = (1000, 1000), raw = true, camera = campixel!)
display(scene)
Line 1,570 ⟶ 3,363:
score_text = @lift("Score: $(length($game.snake)-1)")
text!(scene, score_text, color=:gray, position = @lift((widths($area)[1]/2, widths($area)[2])),
direction = Ref{Any}(nothing)
on(events(scene).
if
direction[] = CartesianIndex(-1,0)
direction[] = CartesianIndex(0,1)
direction[] = CartesianIndex(0,-1)
direction[] = CartesianIndex(1,0)
end
end
end
Line 1,604 ⟶ 3,399:
play()
</syntaxhighlight>
=={{header|Kotlin}}==
{{trans|C++}}
{{works with|Windows 10}}
<
import kotlinx.cinterop.*
Line 1,790 ⟶ 3,585:
srand(time(null).toInt())
Snake().play()
}</
{{output}}
Line 1,796 ⟶ 3,591:
Similar to C++ entry
</pre>
=={{header|Lua}}==
[[File:snake-lua.png]]
{{works with|LÖVE}}
<syntaxhighlight lang="lua">UP, RIGHT, DOWN, LEFT = 1, 2, 3, 4
UpdateTime=0.200
Timer = 0
GridSize = 30
GridWidth, GridHeight = 20, 10
local directions = {
[UP] = {x= 0, y=-1},
[RIGHT] = {x= 1, y= 0},
[DOWN] = {x= 0, y= 1},
[LEFT] = {x=-1, y= 0},
}
local function isPositionInBody(x, y)
for i = 1, #Body-3, 2 do -- skip tail, it moves before we get in
if x == Body[i] and y == Body[i+1] then
return true
end
end
return false
end
local function isPositionInApple(x, y)
if x == Apple.x and y == Apple.y then
return true
end
return false
end
local function newApple ()
local ApplePlaced = false
while not ApplePlaced do
local x = GridSize*math.random (GridWidth)
local y = GridSize*math.random (GridHeight)
if not isPositionInBody(x, y) then
Apple = {x=x, y=y}
ApplePlaced = true
end
end
end
local function newGame ()
Score = 0
GameOver = false
local x = GridSize*math.floor(math.random (0.25*GridWidth, 0.75*GridWidth))
print (x)
local y = GridSize*math.floor(math.random (0.25*GridHeight, 0.75*GridHeight))
print (y)
local iDirection = math.random(4)
local d = directions[iDirection]
Head = {
x=x,
y=y,
iDirection = iDirection,
nextDirection = iDirection,
}
Body = {x, y, x-GridSize*d.x, y-GridSize*d.y}
Apples = {}
newApple ()
end
function love.load()
newGame ()
end
local function moveSnake (x, y, iDirection, longer)
table.insert (Body, 1, x)
table.insert (Body, 2, y)
Head.x = x
Head.y = y
Head.iDirection = iDirection
if not longer then
-- remove last pair
table.remove(Body)
table.remove(Body)
end
if x <= 0 or x > GridSize*(GridWidth) or
y <= 0 or y > GridSize*(GridHeight) then
GameOver = true
end
end
function love.update(dt)
Timer = Timer + dt
if Timer < UpdateTime then return end
Timer = Timer - UpdateTime
local iDirection = Head.nextDirection
local d = directions[iDirection]
local x, y = Head.x+GridSize*d.x, Head.y+GridSize*d.y
if isPositionInBody(x, y) then
GameOver = true
elseif isPositionInApple(x, y) then
Score = Score + 1
newApple ()
moveSnake (x, y, iDirection, true)
else
moveSnake (x, y, iDirection, false)
end
end
function drawHead () -- position, length, width and angle
love.graphics.push()
love.graphics.translate(Head.x, Head.y)
love.graphics.rotate((Head.iDirection-2)*math.pi/2)
love.graphics.polygon("fill",
-GridSize/3, -GridSize /3,
-GridSize/3, GridSize /3,
GridSize/3, 0)
love.graphics.pop()
end
function love.draw()
love.graphics.setColor(0,1,0)
love.graphics.print ('Score: '..tostring(Score), 10, 10)
if GameOver then
love.graphics.print ('Game Over: '..tostring(GameOver)..'. Press "Space" to continue', 10, 30)
else
love.graphics.translate(GridSize, GridSize)
love.graphics.setColor(0.6,0.6,0.6)
love.graphics.setLineWidth(0.25)
for x = GridSize, GridSize*GridWidth, GridSize do
love.graphics.line (x, GridSize, x, GridSize*GridHeight)
end
for y = GridSize, GridSize*GridHeight, GridSize do
love.graphics.line (GridSize, y, GridSize*GridWidth, y)
end
love.graphics.setLineWidth((GridSize/4)+0.5)
love.graphics.setColor(1,1,1)
love.graphics.line (Body)
drawHead ()
love.graphics.setColor(1,0,0)
love.graphics.circle ('fill', Apple.x, Apple.y, GridSize/4)
end
end
function love.keypressed(key, scancode, isrepeat)
if false then
elseif key == "space" then
if GameOver then
GameOver = false
newGame ()
end
elseif key == "escape" then
love.event.quit()
else
local iDirection = Head.iDirection
if iDirection == UP or
iDirection == DOWN then
local right = love.keyboard.isScancodeDown ("d")
local left = love.keyboard.isScancodeDown ("a")
if right and not left then
iDirection = RIGHT
elseif left and not right then
iDirection = LEFT
end
else -- right or left
local down = love.keyboard.isScancodeDown ("s")
local up = love.keyboard.isScancodeDown ("w")
if up and not down then
iDirection = UP
elseif down and not up then
iDirection = DOWN
end
end
Head.nextDirection = iDirection
end
end</syntaxhighlight>
=={{header|Nim}}==
{{trans|C}}
{{libheader|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”.
<syntaxhighlight 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()</syntaxhighlight>
=={{header|OCaml}}==
Line 1,801 ⟶ 3,883:
{{libheader|OCamlSDL2}}
<
open Sdl
Line 1,954 ⟶ 4,036:
main_loop state
in
main_loop initial_state</
=={{header|Perl}}==
[[File:Snake_game_perl.png|200px|thumb|right]]
<
use Time::HiRes qw(sleep);
use Term::ANSIColor qw(colored);
Line 2,128 ⟶ 4,210:
elsif ($key eq "\e[C" and $dir ne LEFT ) { $dir = RIGHT }
elsif ($key eq "\e[D" and $dir ne RIGHT) { $dir = LEFT }
}</
=={{header|Phix}}==
{{trans|C++}}
<
enum NORTH, EAST, SOUTH, WEST
Line 2,212 ⟶ 4,294:
end while
end procedure
play()</
=={{header|Python}}==
Using Pygame. Works with Python >= 3.7.
<syntaxhighlight 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)
</syntaxhighlight>
=={{header|Raku}}==
Line 2,219 ⟶ 4,566:
This is a variation of a demo script included in the examples folder for the Raku [https://modules.raku.org/search/?q=SDL2%3A%3ARaw SDL2::Raw library bindings].
<syntaxhighlight lang="raku"
use Cairo;
Line 2,393 ⟶ 4,740:
}
SDL_Quit();</
=={{header|Rust}}==
Implemented smooth (per-pixel) animation on Win32 API (tested on Windows 7 and Windows 11)
<syntaxhighlight lang="rust">/* add to file Cargo.toml:
[dependencies]
winsafe = "0.0.8" # IMHO: before the appearance of winsafe="0.1" it is not worth raising the version here
rand = "0.8"
derive-new = "0.5"
*/
#![windows_subsystem = "windows"]
use derive_new::new;
use rand::{thread_rng, Rng};
use std::{cell::RefCell, rc::Rc};
use winsafe::{co, gui, prelude::*, COLORREF, HBRUSH, HPEN, RECT, SIZE};
const STEP: i32 = 3; // px, motion per frame
const GCW: i32 = 7; // game grid cell width in STEPs
const SNAKE_W: i32 = 20; // px
const FW: i32 = 20; // the width of the square field in the cells of the game grid
const TW: i32 = (FW + 2) * GCW; // total field width (with overlap for collisions) in STEPs
const ID0: i32 = FW / 2 * GCW; // starting position id
#[rustfmt::skip]
#[derive(new)]
struct Context {
wnd: gui::WindowMain,
#[new(default) ] snake: Vec<i32>, // [rect_ids] where rect_id = y * TW + x (where x, y: nSTEPs)
#[new(value = "[ID0; 6]")] r: [i32; 6], // ID 6 rect to color in next frame (bg, tail, turn, body, food, head)
#[new(default) ] incr: i32, // 0 | -1 | 1 | -TW | TW - increment r[head] in next STEP
#[new(value = "TW") ] next_incr: i32, // `incr` in the next grid cell
#[new(default) ] gap: i32, // interval in STEPs to the next grid cell; negative - tail clipping mark
}
pub fn main() {
let [bg, tail, turn, body, food, head] = [0_usize, 1, 2, 3, 4, 5];
let grid: Vec<_> = (1..=FW).flat_map(|y| (1..=FW).map(move |x| (y * TW + x) * GCW)).collect();
let mut colors = [(0x00, 0xF0, 0xA0); 6]; // color tail, turn, body
colors[bg] = (0x00, 0x50, 0x90);
colors[food] = (0xFF, 0x50, 0x00);
colors[head] = (0xFF, 0xFF, 0x00);
let brushes = COLORREF::new_array(&colors).map(|c| HBRUSH::CreateSolidBrush(c).unwrap());
let wnd = gui::WindowMain::new(gui::WindowMainOpts {
title: "Snake - Start: Space, then press W-A-S-D".to_string(),
size: SIZE::new(FW * GCW * STEP, FW * GCW * STEP),
class_bg_brush: brushes[bg],
..Default::default()
});
// WindowMain is based on Arc, so wnd.clone() is a shallow copy of a reference.
let context = Rc::new(RefCell::new(Context::new(wnd.clone())));
wnd.on().wm_paint({
let context = Rc::clone(&context);
move || {
let ctx = context.borrow();
let mut ps = winsafe::PAINTSTRUCT::default();
let hdc = ctx.wnd.hwnd().BeginPaint(&mut ps)?;
hdc.SelectObjectPen(HPEN::CreatePen(co::PS::NULL, 0, COLORREF::new(0, 0, 0))?)?;
for (&id, &brush) in ctx.r.iter().zip(&brushes[bg..=head]) {
let [left, top] = [id % TW, id / TW].map(|i| i * STEP - (STEP * GCW + SNAKE_W) / 2);
let rect = RECT { left, top, right: left + SNAKE_W, bottom: top + SNAKE_W };
hdc.SelectObjectBrush(brush)?;
hdc.RoundRect(rect, SIZE::new(SNAKE_W / 2, SNAKE_W / 2))?;
}
Ok(ctx.wnd.hwnd().EndPaint(&ps))
}
});
wnd.on().wm_key_down({
let context = Rc::clone(&context);
move |key| {
let mut ctx = context.borrow_mut();
Ok(match (ctx.incr.abs(), key.char_code as u8) {
(0, b' ') => _ = ctx.wnd.hwnd().SetTimer(1, 10, None)?, // Start / Restart
(TW, bt @ (b'A' | b'D')) => ctx.next_incr = if bt == b'A' { -1 } else { 1 },
(1, bt @ (b'S' | b'W')) => ctx.next_incr = if bt == b'S' { TW } else { -TW },
_ => (),
})
}
});
wnd.on().wm_timer(1, move || {
let mut ctx = context.borrow_mut();
let new_h = ctx.r[head] + ctx.incr;
(ctx.r[body], ctx.r[head]) = (ctx.r[head], new_h);
if ctx.gap < 0 {
ctx.r[bg] = ctx.snake.remove(0);
ctx.r[tail] = ctx.snake[0];
ctx.r[turn] = ctx.snake[GCW as usize / 2];
}
ctx.gap -= ctx.gap.signum();
if ctx.gap == 0 {
ctx.gap = if new_h == ctx.r[food] { GCW } else { -GCW };
let mut snake_cells: Vec<_> = ctx.snake.iter().step_by(GCW as usize).collect();
if new_h == ctx.r[food] {
ctx.wnd.hwnd().SetWindowText(&format!("Snake - Eaten: {}", snake_cells.len()))?;
snake_cells.sort();
ctx.r[food] = *(grid.iter())
.filter(|i| **i != new_h && snake_cells.binary_search(i).is_err())
.nth(thread_rng().gen_range(0..(grid.len() - 1 - snake_cells.len()).max(1)))
.unwrap_or(&0);
} else if grid.binary_search(&new_h).is_err() || snake_cells.contains(&&new_h) {
ctx.wnd.hwnd().KillTimer(1)?; // Stop
let title = ctx.wnd.hwnd().GetWindowText()?;
ctx.wnd.hwnd().SetWindowText(&(title + ". Restart: Space"))?;
*ctx = Context::new(ctx.wnd.clone());
return Ok(());
}
ctx.incr = ctx.next_incr;
}
ctx.snake.push(new_h);
ctx.wnd.hwnd().InvalidateRect(None, new_h == ID0)?; // call .wm_paint(), with erase on Restart
Ok(())
});
wnd.run_main(None).unwrap();
}</syntaxhighlight>
{{out}}
[[File:Snake rust.png]]
=={{header|Sidef}}==
<
const readkey = frequire('Term::ReadKey')
const ansi = frequire('Term::ANSIColor')
Line 2,532 ⟶ 5,000:
var h = `tput lines`.to_i
SnakeGame(w || 80, h || 24).play</
=={{header|UNIX Shell}}==
{{works with|Bourne Again SHell}}
{{works with|Z Shell}}
Play ASCII snake in your ANSI terminal.
It theoretically should also work with the Korn Shell, but for some reason the timeout on the key read is not consistent and the snake stops moving except one step after each keypress.
<syntaxhighlight lang="bash">function main {
typeset -i game_over=0
typeset -i height=$(tput lines) width=$(tput cols)
# start out in the middle moving to the right
typeset -i dx dy hx=$(( width/2 )) hy=$(( height/2 ))
typeset -a sx=($hx) sy=($hy)
typeset -a timeout
clear
tput cup "$sy" "$sx" && printf '@'
tput cup $(( height/2+2 )) 0
center $width "Press h, j, k, l to move left, down, up, right"
# place first food
typeset -i fx=hx fy=hy
while (( fx == hx && fy == hy )); do
fx=$(( RANDOM % (width-2)+1 )) fy=$(( RANDOM % (height-2)+1 ))
done
tput cup "$fy" "$fx" && printf '*'
# handle variations between shells
keypress=(-N 1) origin=0
if [[ -n $ZSH_VERSION ]]; then
keypress=(-k)
origin=1
fi
stty -echo
tput civis
typeset key
read "${keypress[@]}" -s key
typeset -i start_time=$(date +%s)
tput cup "$(( height/2+2 ))" 0 && tput el
while (( ! game_over )); do
timeout=(-t $(printf '0.%04d' $(( 2000 / (${#sx[@]}+1) )) ) )
if [[ -z $key ]]; then
read "${timeout[@]}" "${keypress[@]}" -s key
fi
case "$key" in
h) if (( dx != 1 )); then dx=-1; dy=0; fi;;
j) if (( dy != -1 )); then dy=1; dx=0; fi;;
k) if (( dy != 1 )); then dy=-1; dx=0; fi;;
l) if (( dx != -1 )); then dx=1; dy=0; fi;;
q) game_over=1; tput cup 0 0 && print "Final food was at ($fx,$fy)";;
esac
key=
(( hx += dx, hy += dy ))
# if we try to go off screen, game over
if (( hx < 0 || hx >= width || hy < 0 || hy >= height )); then
game_over=1
else
# if we run into ourself, game over
for (( i=0; i<${#sx[@]}; ++i )); do
if (( hx==sx[i+origin] && hy==sy[i+origin] )); then
game_over=1
break
fi
done
fi
if (( game_over )); then
break
fi
# add new spot
sx+=($hx) sy+=($hy)
if (( hx == fx && hy == fy )); then
# if we just ate some food, place some new food out
ok=0
while (( ! ok )); do
# make sure we don't put it under ourselves
ok=1
fx=$(( RANDOM % (width-2)+1 )) fy=$(( RANDOM % (height-2)+1 ))
for (( i=0; i<${#sx[@]}; ++i )); do
if (( fx == sx[i+origin] && fy == sy[i+origin] )); then
ok=0
break
fi
done
done
tput cup "$fy" "$fx" && printf '*'
# and don't remove our tail because we've just grown by 1
else
# if we didn't just eat food, remove our tail from its previous spot
tput cup ${sy[origin]} ${sx[origin]} && printf ' '
sx=( ${sx[@]:1} )
sy=( ${sy[@]:1} )
fi
# draw our new head
tput cup "$hy" "$hx" && printf '@'
done
typeset -i end_time=$(date +%s)
tput cup $(( height / 2 -1 )) 0 && center $width 'GAME OVER'
tput cup $(( height / 2 )) 0 &&
center $width 'Time: %d seconds' $(( end_time - start_time ))
tput cup $(( height / 2 + 1 )) 0 &&
center $width 'Final length: %d' ${#sx[@]}
echo
stty echo
tput cnorm
}
function center {
typeset -i width=$1 i
shift
typeset message=$(printf "$@")
tput cuf $(( (width-${#message}) / 2 ))
printf '%s' "$message"
}
main "$@"</syntaxhighlight>
=={{header|Wren}}==
{{trans|C}}
{{libheader|ncurses}}
{{libheader|Wren-dynamic}}
An embedded program so we can ask the C host to call ncurses and another library function for us.
<syntaxhighlight lang="wren">/* 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()</syntaxhighlight>
<br>
Now embed this script in the following C program, compile and run it.
<syntaxhighlight lang="c">/* gcc Snake.c -o Snake -lncurses -lwren -lm */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ncurses.h>
#include <unistd.h>
#include "wren.h"
/* C <=> Wren interface functions */
void C_windowAllocate(WrenVM* vm) {
WINDOW** pwin = (WINDOW**)wrenSetSlotNewForeign(vm, 0, 0, sizeof(WINDOW*));
*pwin = initscr();
}
void C_nodelay(WrenVM* vm) {
WINDOW* win = *(WINDOW**)wrenGetSlotForeign(vm, 0);
bool bf = wrenGetSlotBool(vm, 1);
nodelay(win, bf);
}
void C_cbreak(WrenVM* vm) {
cbreak();
}
void C_noecho(WrenVM* vm) {
noecho();
}
void C_refresh(WrenVM* vm) {
refresh();
}
void C_getch(WrenVM* vm) {
int ch = getch();
wrenSetSlotDouble(vm, 0, (double)ch);
}
void C_mvaddch(WrenVM* vm) {
int y = (int)wrenGetSlotDouble(vm, 1);
int x = (int)wrenGetSlotDouble(vm, 2);
const chtype ch = (const chtype)wrenGetSlotDouble(vm, 3);
mvaddch(y, x, ch);
}
void C_endwin(WrenVM* vm) {
endwin();
}
void C_usleep(WrenVM* vm) {
useconds_t usec = (useconds_t)wrenGetSlotDouble(vm, 1);
usleep(usec);
}
WrenForeignClassMethods bindForeignClass(WrenVM* vm, const char* module, const char* className) {
WrenForeignClassMethods methods;
methods.allocate = NULL;
methods.finalize = NULL;
if (strcmp(module, "main") == 0) {
if (strcmp(className, "Window") == 0) {
methods.allocate = C_windowAllocate;
}
}
return methods;
}
WrenForeignMethodFn bindForeignMethod(
WrenVM* vm,
const char* module,
const char* className,
bool isStatic,
const char* signature) {
if (strcmp(module, "main") == 0) {
if (strcmp(className, "Window") == 0) {
if (!isStatic && strcmp(signature, "nodelay(_)") == 0) return C_nodelay;
} else if (strcmp(className, "Ncurses") == 0) {
if (isStatic && strcmp(signature, "cbreak()") == 0) return C_cbreak;
if (isStatic && strcmp(signature, "noecho()") == 0) return C_noecho;
if (isStatic && strcmp(signature, "refresh()") == 0) return C_refresh;
if (isStatic && strcmp(signature, "getch()") == 0) return C_getch;
if (isStatic && strcmp(signature, "mvaddch(_,_,_)") == 0) return C_mvaddch;
if (isStatic && strcmp(signature, "endwin()") == 0) return C_endwin;
} else if (strcmp(className, "C") == 0) {
if (isStatic && strcmp(signature, "usleep(_)") == 0) return C_usleep;
}
}
return NULL;
}
static void writeFn(WrenVM* vm, const char* text) {
printf("%s", text);
}
void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {
switch (errorType) {
case WREN_ERROR_COMPILE:
printf("[%s line %d] [Error] %s\n", module, line, msg);
break;
case WREN_ERROR_STACK_TRACE:
printf("[%s line %d] in %s\n", module, line, msg);
break;
case WREN_ERROR_RUNTIME:
printf("[Runtime Error] %s\n", msg);
break;
}
}
char *readFile(const char *fileName) {
FILE *f = fopen(fileName, "r");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
rewind(f);
char *script = malloc(fsize + 1);
fread(script, 1, fsize, f);
fclose(f);
script[fsize] = 0;
return script;
}
static void loadModuleComplete(WrenVM* vm, const char* module, WrenLoadModuleResult result) {
if( result.source) free((void*)result.source);
}
WrenLoadModuleResult loadModule(WrenVM* vm, const char* name) {
WrenLoadModuleResult result = {0};
if (strcmp(name, "random") != 0 && strcmp(name, "meta") != 0) {
result.onComplete = loadModuleComplete;
char fullName[strlen(name) + 6];
strcpy(fullName, name);
strcat(fullName, ".wren");
result.source = readFile(fullName);
}
return result;
}
int main(int argc, char **argv) {
WrenConfiguration config;
wrenInitConfiguration(&config);
config.writeFn = &writeFn;
config.errorFn = &errorFn;
config.bindForeignClassFn = &bindForeignClass;
config.bindForeignMethodFn = &bindForeignMethod;
config.loadModuleFn = &loadModule;
WrenVM* vm = wrenNewVM(&config);
const char* module = "main";
const char* fileName = "Snake.wren";
char *script = readFile(fileName);
WrenInterpretResult result = wrenInterpret(vm, module, script);
switch (result) {
case WREN_RESULT_COMPILE_ERROR:
printf("Compile Error!\n");
break;
case WREN_RESULT_RUNTIME_ERROR:
printf("Runtime Error!\n");
break;
case WREN_RESULT_SUCCESS:
break;
}
wrenFreeVM(vm);
free(script);
return 0;
}</syntaxhighlight>
=={{header|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.
<syntaxhighlight 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
]</syntaxhighlight>
|
Revision as of 12:31, 6 July 2024
![Task](http://static.miraheze.org/rosettacodewiki/thumb/b/ba/Rcode-button-task-crushed.png/64px-Rcode-button-task-crushed.png)
You are encouraged to solve this task according to the task description, using any language you may know.
This page uses content from Wikipedia. The original article was at Snake_(video_game). The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance) |
Snake is a game where the player maneuvers a line which grows in length every time the snake reaches a food source.
- Task
Implement a variant of the Snake game, in any interactive environment, in which a sole player attempts to eat items by running into them with the head of the snake.
Each item eaten makes the snake longer and a new item is randomly generated somewhere else on the plane.
The game ends when the snake attempts to eat himself.
Ada
-- This code is a basic implementation of snake in Ada using the command prompt
-- feel free to improve it!
-- Snake.ads
with Ada.Containers; use Ada.Containers;
with Ada.Containers.Vectors;
package Snake is
-- copy from 2048 game (
-- Keyboard management
type directions is (Up, Down, Right, Left, Quit, Restart, Invalid);
-- Redefining this standard procedure as function to allow Get_Keystroke as an expression function
function Get_Immediate return Character;
Arrow_Prefix : constant Character := Character'Val(224); -- works for windows
function Get_Keystroke return directions;
-- )
-- The container for game data
type gameBoard is array (Natural range <>, Natural range <>) of Character;
-- Initilize the board
procedure init;
-- Run the game
procedure run;
-- Displaying the board
procedure Display_Board;
-- Clear the board from content
procedure ClearBoard;
-- coordinates data structure
type coord is tagged record
X,Y : Integer;
end record;
-- list of coordinate (one coordinate for each body part of the snake)
package snakeBody is new Ada.Containers.Vectors (Natural, coord);
use snakeBody;
-- update snake's part depending on the snakeDirection and checking colicion (with borders and snak's part)
function moveSnake return Boolean;
-- generate food if it was eaten
procedure generateFood;
-- Add snake and food to the board
procedure addDataToBoard;
-- generate random integer between 1 and upperBound to generate random food position
function getRandomInteger(upperBound : Integer) return Integer;
private
width, height : Positive := 10;
board : gameBoard := (0 .. (width+1) => (0 .. (height+1) => ' '));
snake : Vector := (1,1) & (2,1) & (3,1);
snakeDirection : directions := Right;
food : coord := (5,5);
end Snake;
-- Snake.adb
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Numerics.Discrete_Random;
package body Snake is
-----------------------------------------------------------------------------
-- Redefining this standard procedure as function to allow Get_Keystroke as an expression function (copy from 2048 game)
function Get_Immediate return Character is
begin
return Answer : Character do Ada.Text_IO.Get_Immediate(Answer);
end return;
end Get_Immediate;
-----------------------------------------------------------------------------
-- (copy from 2048 game)
function Get_Keystroke return directions is
(case Get_Immediate is
when 'Q' | 'q' => Quit,
when 'R' | 'r' => Restart,
when 'W' | 'w' => Left,
when 'A' | 'a' => Up,
when 'S' | 's' => Down,
when 'D' | 'd' => Right,
-- Windows terminal
when Arrow_Prefix => (case Character'Pos(Get_Immediate) is
when 72 => Up,
when 75 => Left,
when 77 => Right,
when 80 => Down,
when others => Invalid),
-- Unix escape sequences
when ASCII.ESC => (case Get_Immediate is
when '[' => (case Get_Immediate is
when 'A' => Up,
when 'B' => Down,
when 'C' => Right,
when 'D' => Left,
when others => Invalid),
when others => Invalid),
when others => Invalid);
-----------------------------------------------------------------------------
procedure init is
begin
for Row in 0 .. width+1 loop
for Column in 0 .. height+1 loop
if Row = 0 or Row = width+1 then
board(Row,Column) := '#';
-- Insert(board(Row,0), Column, '#');
else
if Column = 0 or Column = height+1 then
board(Row,Column) := '#';
-- Insert(board, board(Row, Column), "#");
else
board(Row,Column) := ' ';
-- Insert(board, board(Row, Column), " ");
end if;
end if;
end loop;
end loop;
end;
-----------------------------------------------------------------------------
procedure run is
task T;
task body T is
begin
loop
snakeDirection := Get_Keystroke;
end loop;
end T;
begin
init;
loop
exit when snakeDirection = Quit;
if moveSnake = False then
Put_Line("GAME OVER!!!");
Put_Line("Your score is:" & Length(snake)'Image);
exit;
end if;
Display_Board;
delay 0.7;
end loop;
end run;
-----------------------------------------------------------------------------
procedure Display_Board is
begin
for Row in 0 .. width+1 loop
for Column in 0 .. height+1 loop
Put(board(Row, Column));
end loop;
New_Line;
end loop;
end Display_Board;
-----------------------------------------------------------------------------
procedure ClearBoard is
begin
for Row in 1 .. width loop
for Column in 1 .. height loop
board(Row, Column) := ' ';
end loop;
end loop;
end ClearBoard;
-----------------------------------------------------------------------------
function moveSnake return Boolean is
colision : Boolean := False;
headCoord : coord := Snake.First_Element;
addSnakePart : Boolean := False;
begin
case snakeDirection is
when Up => headCoord.X := headCoord.X - 1;
when Down => headCoord.X := headCoord.X + 1;
when Right => headCoord.Y := headCoord.Y + 1;
when Left => headCoord.Y := headCoord.Y - 1;
when others => null;
end case;
if headCoord.Y = height+1 then
return False;
end if;
if headCoord.Y = 0 then
return False;
end if;
if headCoord.X = width+1 then
return False;
end if;
if headCoord.X = 0 then
return False;
end if;
for index in snake.Iterate loop
if headCoord = snake(To_Index(index)) then
return False;
end if;
end loop;
snake.Prepend(headCoord);
if headCoord /= food then
snake.Delete_Last;
else
food := (0,0);
generateFood;
end if;
addDataToBoard;
return True;
end moveSnake;
-----------------------------------------------------------------------------
procedure generateFood is
begin
if food.X = 0 or food.Y = 0 then
food := (getRandomInteger(width), getRandomInteger(height));
end if;
end generateFood;
-----------------------------------------------------------------------------
procedure addDataToBoard is
begin
ClearBoard;
board(food.X, food.Y) := '*';
for index in snake.Iterate loop
board(snake(To_Index(index)).X, snake(To_Index(index)).Y) := 'o';
end loop;
end addDataToBoard;
-----------------------------------------------------------------------------
function getRandomInteger(upperBound : Integer) return Integer is
subtype Random_Range is Integer range 1 .. upperBound;
package R is new
Ada.Numerics.Discrete_Random (Random_Range);
use R;
G : Generator;
begin
Reset (G);
return Random (G);
end getRandomInteger;
end Snake;
-- main.adb
with Snake;
procedure Main is
begin
Snake.run;
end;
- Output:
############ # # # # # ooo # # o # # o # # o # # o # # o # # * # # # ############
Amazing Hopper
/* Snake */
/* Implementing this task in Hopper-FLOW-MATIC++ */
/* The snake must catch a bite before time runs out, which decreases by
10 points every 800 milliseconds.
The remaining time will be added to your total score. */
#include <flow.h>
#include <flow-term.h>
#include <keys.h>
#define LIMIT_TIME 120
#define INF_TIME 80 //70
#define ONE_SECOND 1000
#define TOTAL_TIME 100
#define DECREMENT_TIME 10
#define COLOR_FOOD 232
#define BACK_FOOD 255
#define TIME_LEVEL 90000 //90000 // un minuto y medio
#define TIME_CHALLENGE 40000 // 40 segundos
#define TIME_LOST 30000
#define COLUMNA_PRINT 55
#define SIZE_INITIAL -7
#enum 1,N,E,S,W
#enum 1,SPACE,FOOD,BORDER
DEF-MAIN
BREAK-ON
STACK 16
MSET(C, quit, nHead, dir, Size, SCORE, counter, T, TPlay,TSound, ConsPrey,Consumed stage )
SET(len,0)
SET( w, 50)
SET( h, 28)
SET( color food, COLOR_FOOD )
SET( back food, BACK_FOOD )
SET( TIME, 100 )
LET( Size := MUL(w,h) )
SET( LEVEL,1 )
SET( REAL LEVEL,1 )
FALSE(swPierde)
TRUE(swExtra1,swExtra2)
TRUE( tiles )
SET( back tiles,1 ), MSET(tile1, tile2)
VOID( head back tiles )
MEM("\033[48;5;28m","\033[48;5;29m","\033[48;5;62m","\033[48;5;63m","\033[48;5;70m","\033[48;5;71m","\033[48;5;4m","\033[48;5;27m" )
MEM("\033[48;5;99m","\033[48;5;97m","\033[48;5;17m","\033[48;5;18m","\033[48;5;62m","\033[48;5;63m")
APND-LST(head back tiles)
[back tiles] GET(head back tiles), MOVE-TO(tile1)
[PLUS-ONE(back tiles)] GET(head back tiles), MOVE-TO(tile2)
VOID( big number, numL1, numL2, numL3 )
VOID( sounds ), SET( sound index, 1 )
MEM("Snake_music.wav","Snake_music_l3.wav","Snake_music_l5.wav","Snake_music_l4.wav","Snake_music_l6.wav")
APND-LST(sounds)
GOSUB( set score )
MSET(tBoard1,tmpLimit,Maze,wM,hM,SizeM)
DIM( Size ) AS-ONES( board ), {board} MOVE-TO(tBoard1)
// load and prepare maze for challenge stage:
SET(i,1)
LET( Maze := REPLICATE("3",50) )
PERF-UP(i,26,1)
LET( Maze := CAT( Maze, CAT( CAT("3", REPLICATE("1",48) ), "3") ) )
NEXT
LET( Maze := CAT( Maze, REPLICATE("3",50) ))
GOSUB( prepare maze )
HIDE-CURSOR
CLR-SCR
SET( TLimit := LIMIT_TIME )
MEM(SIZE_INITIAL),GOSUB( start )
SET( time out, TIME_LEVEL)
SYS( CAT(CAT("aplay fl/",[sound index]CGET(sounds)), "</dev/null >/dev/null 2>&1 &"))
GOSUB( titles )
/* PLAY GAME!! */
SET(lives,3)
WHILE( lives )
GOSUB( show )
GOSUB( put titles )
GOSUB( ready ), SLEEP(1.5)
TIC( T ), TIC( TPlay ), TIC(TSound)
KBD-FREE
WHILE( NOT (quit) )
ON-TIME( TLimit ~= TPlay ){
GOSUB( show )
WHEN( KEY-PRESSED? ){
SCAN-CODE( C )
SELECT( C )
CASE( K_UP ) { dir = N, EXIT }
CASE( K_RIGHT ){ dir = E, EXIT }
CASE( K_DOWN ) { dir = S, EXIT }
CASE( K_LEFT ) { dir = W, EXIT }
CASE( K_ESC ) { quit = 1, EXIT }
CASE( 32 ) { PAUSE, EXIT }
SELEND
}
GOSUB( step )
}
ON-TIME( ONE_SECOND ~= T ) {
TIME-=DECREMENT_TIME, CLAMP(0,TOTAL_TIME,TIME)
{TIME, 12, COLUMNA_PRINT} GOSUB( put time )
}
ON-TIME( time out ~= TSound ){
GOSUB(LEVELCLEAR)
}
WEND
COND( OR(EQ?(LEVEL,3),EQ?(LEVEL,6)) )
GOSUB(you lost),SLEEP(3)
SET(quit,0), [nHead]{len}CPUT(board)
LOCATE(1,1), FILL-BOX(" ",29,54)
GOSUB( another play )
ELS
GOSUB(you lost), SLEEP(3)
--lives
SET(quit,0)
TRUE(swPierde)
{len}GOSUB( start )
COND( IS-NOT-ZERO?(lives) )
SYS("aplay fl/Snake_music_vida_repechaje.wav </dev/null >/dev/null 2>&1 &")
CEND
CEND
WEND
/* GAME OVER */
GOSUB(game over ), SLEEP(7)
SET-VIDEO-TEXT
SHOW-CURSOR
PRNL
END
RUTINES
DEF-FUN( KILLSOUND )
SET( PID,0 )
LET( PID := ONLY-CHR("0123456789 ", EXEC( "pidof aplay" )))
COND( LEN(PID) )
SYS( CAT(CAT("kill ",PID), " </dev/null >/dev/null 2>&1"))
CEND
RET
// initialize the board, plant a very first food item
DEF-FUN( start, initial pos )
COND( NOT-EQ?(LEVEL,3) )
{tBoard1} MOVE-TO(board)
{50,28},MUL(50,28), MOVE-TO( w, h, Size )
[1:w] {BORDER} CPUT(board) // top
[ SUB(MUL(h,w),MINUS-ONE(w)) : end] {BORDER} CPUT(board) // bottom
[1:w:end] {BORDER} CPUT(board) // left
SET(i, 1)
FOR( LE?(i, h), ++i )
[ MUL(i, w )] {BORDER} PUT(board) // right
NEXT
LET( time out:=TIME_LEVEL )
ELS
TRASH(board)
CEND
SELECT(LEVEL)
CASE(2){
SET(i,3)
PERF-UP(i,5,1)
// laterales extremos:
[ADD(MUL(i,w),5):ADD(MUL(i,w),8)] {BORDER} CPUT(board)
[ADD(MUL(i,w),SUB(w,7)):ADD(MUL(i,w),SUB(w,4))] {BORDER} CPUT(board)
// laterales fondo
[ADD(MUL(ADD(i,19),w),5):ADD(MUL(ADD(i,19),w),8)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,19),w),SUB(w,7)):ADD(MUL(ADD(i,19),w),SUB(w,4))] {BORDER} CPUT(board)
NEXT
EXIT
}
CASE(3){ // challenge stage!
SPLIT(Maze,board, "" ), VAL(board), MOVE-TO(board)
[1:w] {BORDER}CPUT(board)
SET(i,1)
PERF-UP(i,19,1)
GOSUB(plant)
NEXT
LET(time out := TIME_CHALLENGE)
EXIT
}
CASE(4){
SET(i,3)
PERF-UP(i,5,1)
// laterales medios
[ADD(MUL(ADD(i,9),w),5):ADD(MUL(ADD(i,9),w),8)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,9),w),SUB(w,7)):ADD(MUL(ADD(i,9),w),SUB(w,4))] {BORDER} CPUT(board)
// medio arriba
[ADD(MUL(i,w),17):ADD(MUL(i,w),20)] {BORDER} CPUT(board)
[ADD(MUL(i,w),31):ADD(MUL(i,w),34)] {BORDER} CPUT(board)
// medio abajo
[ADD(MUL(ADD(i,19),w),17):ADD(MUL(ADD(i,19),w),20)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,19),w),31):ADD(MUL(ADD(i,19),w),34)] {BORDER} CPUT(board)
NEXT
EXIT
}
CASE(5){
SET(i,3)
PERF-UP(i,5,1)
// laterales extremos:
[ADD(MUL(i,w),5):ADD(MUL(i,w),8)] {BORDER} CPUT(board)
[ADD(MUL(i,w),SUB(w,7)):ADD(MUL(i,w),SUB(w,4))] {BORDER} CPUT(board)
// laterales medios
[ADD(MUL(ADD(i,9),w),5):ADD(MUL(ADD(i,9),w),8)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,9),w),SUB(w,7)):ADD(MUL(ADD(i,9),w),SUB(w,4))] {BORDER} CPUT(board)
// laterales fondo
[ADD(MUL(ADD(i,19),w),5):ADD(MUL(ADD(i,19),w),8)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,19),w),SUB(w,7)):ADD(MUL(ADD(i,19),w),SUB(w,4))] {BORDER} CPUT(board)
// medio arriba
[ADD(MUL(i,w),17):ADD(MUL(i,w),20)] {BORDER} CPUT(board)
[ADD(MUL(i,w),31):ADD(MUL(i,w),34)] {BORDER} CPUT(board)
// medio abajo
[ADD(MUL(ADD(i,19),w),17):ADD(MUL(ADD(i,19),w),20)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,19),w),31):ADD(MUL(ADD(i,19),w),34)] {BORDER} CPUT(board)
NEXT
EXIT
}
CASE(6){
SET(i,1)
PERF-UP(i,29,1)
GOSUB(plant)
NEXT
LET(time out := TIME_CHALLENGE)
}
CASE(7){
SET(i,3)
PERF-UP(i,5,1)
// laterales extremos:
[ADD(MUL(i,w),5):ADD(MUL(i,w),8)] {BORDER} CPUT(board)
[ADD(MUL(i,w),SUB(w,7)):ADD(MUL(i,w),SUB(w,4))] {BORDER} CPUT(board)
// laterales medios
[ADD(MUL(ADD(i,9),w),5):ADD(MUL(ADD(i,9),w),8)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,9),w),SUB(w,7)):ADD(MUL(ADD(i,9),w),SUB(w,4))] {BORDER} CPUT(board)
// laterales fondo
[ADD(MUL(ADD(i,19),w),5):ADD(MUL(ADD(i,19),w),8)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,19),w),SUB(w,7)):ADD(MUL(ADD(i,19),w),SUB(w,4))] {BORDER} CPUT(board)
// medio arriba
[ADD(MUL(i,w),17):ADD(MUL(i,w),20)] {BORDER} CPUT(board)
[ADD(MUL(i,w),31):ADD(MUL(i,w),34)] {BORDER} CPUT(board)
// medio medio
[ADD(MUL(ADD(i,9),w),17):ADD(MUL(ADD(i,9),w),20)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,9),w),31):ADD(MUL(ADD(i,9),w),34)] {BORDER} CPUT(board)
// medio abajo
[ADD(MUL(ADD(i,19),w),17):ADD(MUL(ADD(i,19),w),20)] {BORDER} CPUT(board)
[ADD(MUL(ADD(i,19),w),31):ADD(MUL(ADD(i,19),w),34)] {BORDER} CPUT(board)
NEXT
EXIT
}
SELEND
COND( NOT-EQ?(LEVEL,3) )
LET( nHead := MUL( w, SUB( SUB( h, 1 ), MOD(h,2) )) DIV-INTO(2) )
LET( dir := N )
ELS
LET( nHead := ADD( MUL( w, 13 ), 26 ) )
LET( dir := N )
CEND
[ nHead ] {initial pos} CPUT( board )
IF( swPierde, SRAND( ~SECONDS); FALSE(swPierde); {TIME_LOST} »» (time out),\
SRAND( 26785 ) )
GOSUB( plant )
RET
DEF-FUN( you lost )
SET(i,1), SET(k,0), SET(n,1)
FOR( LE?(i, h), ++i)
SET(j,1)
FOR( LE?(j, w), ++j)
LET( k := [ n ] GET(board) )
COND( IS-NEG?( k ))
LOCATE(i,j)
PRN("\033[38;15;3m\033[48;5;9m~\OFF")
CEND
++n
NEXT
NEXT
SYS("aplay fl/Snake_dolor.wav </dev/null >/dev/null 2>&1 &")
RET
DEF-FUN( show )
SET(i,1)
MSET(j, k)
SET(n,1)
LOCATE(1,1)
FOR( LE?(i, h), ++i)
SET(j,1),LOG-INV(tiles)
FOR( LE?(j, w), ++j)
LET( k := [ n ] GET(board) )
COND( IS-NEG?( k ))
COND( NOT-EQ?(n,nHead) )
IF(GT?(k,-3),IF(tiles,{tile1},{tile2});{"\033[38;5;15m+\OFF"},"\033[38;5;6m\033[48;5;11m \OFF")
ELS
COND( EQ?(dir,N))
IF(tiles,"\033[38;5;9m\033[48;5;15mv\OFF","\033[38;5;9m\033[48;5;15m|\OFF")
ELS-COND( EQ?(dir,S))
IF(tiles,"\033[38;5;9m\033[48;5;15m|\OFF","\033[38;5;9m\033[48;5;15m^\OFF")
ELS-COND( EQ?(dir,E))
IF(tiles,"\033[38;5;9m\033[48;5;15m<\OFF","\033[38;5;9m\033[48;5;15m-\OFF")
ELS
IF(tiles,"\033[38;5;9m\033[48;5;15m-\OFF","\033[38;5;9m\033[48;5;15m>\OFF")
CEND
CEND
ELS-COND( {k} IS-EQ?(BORDER))
{"\033[38;5;82m\033[48;5;57m \OFF"}
ELS-COND( {k}IS-EQ?(FOOD) )
COLOR-FG(color food ),IF(tiles,{tile1},{tile2}) //back food)
{"@\OFF"}
ELS
IF(tiles,{tile1},{tile2})
{" \OFF"}
CEND
PRN
LOG-INV(tiles)
++n
NEXT
PRNL
NEXT
color food+=2,
back food-=2, WHEN( EQ?(color food, PLUS-ONE(BACK_FOOD))){
LET( color food:=COLOR_FOOD)
LET( back food:=BACK_FOOD)
}
RET
// negative values denote the snake (a negated time-to-live in given cell)
// reduce a time-to-live, effectively erasing the tail
DEF-FUN( age )
MSET( r, jAge, jR )
CART( IS-NEG?( board ) ) »» (r), SET-RANGE(r)
GET( board ) PLUS(1) »» (jAge)
// this is necessary, because Hopper arrays begining in 1
CART( IS-ZERO?(jAge) ) »» (jR)
COND( IS-ARRAY?(jR) )
SET-RANGE(jR), SET(jAge, 1), SET-RANGE(r)
CEND
// ******
{jAge} PUT(board), CLR-RANGE
RET
DEF-FUN( step )
LET( len := [nHead] GET(board) )
SELECT(dir)
CASE (N){ nHead -= w, EXIT }
CASE (S){ nHead += w, EXIT }
CASE (W){ --nHead, EXIT }
CASE (E){ ++nHead, EXIT }
SELEND
SELECT( [nHead]CGET(board))
CASE (SPACE){
--len, LET( len := IF( EQ?(len,0), 1, len) )
[nHead] { len }, CPUT(board) // keep in mind len is negative
GOSUB( age )
EXIT
}
CASE (FOOD){
COND( NOT(OR(EQ?(LEVEL,3),EQ?(LEVEL,6))))
--len, LET( len := IF( IS-ZERO?(len), 1, len) )
ELS
++len // quita celda del cuerpo: suma vida!
CEND
[nHead] { len }, CPUT(board)
WHEN(AND(NOT-EQ?(LEVEL,3),NOT-EQ?(LEVEL,6))){ GOSUB( plant ) }
ADD(SCORE,TIME), MOVE-TO(SCORE)
{SCORE, 4, COLUMNA_PRINT} GOSUB( put score )
LET( TIME := 100 )
++counter, COND( EQ?( counter, 25 ) )
LET( TLimit := SUB( TLimit,5 ))
CLAMP(INF_TIME, LIMIT_TIME, TLimit)
LET( counter := 0 )
CEND
WHEN( OR(EQ?(LEVEL,3),EQ?(LEVEL,6))){ ++Consumed stage }
++ConsPrey
COLOR-FG(10)
LOCATE(20,COLUMNA_PRINT) PRN("SPEED: ")
LOC-ROW(21), PRN( ROUND(MUL(INV(TLimit),LIMIT_TIME),2), " M/S " )
LOC-ROW(23) PRN("CONSUMED PREY:")
LOC-ROW(24) PRN(ConsPrey,"\OFF")
LET( color food:=COLOR_FOOD)
LET( back food:=BACK_FOOD)
SYS("aplay fl/Snake_mascada.wav </dev/null >/dev/null 2>&1 &")
TIC( T ),{TIME, 12, COLUMNA_PRINT} GOSUB( put time )
EXIT
}
CASE-NEGATIVE {
GOSUB(KILLSOUND)
SYS("aplay fl/Snake_mascada.wav </dev/null >/dev/null 2>&1")
LET( quit := 1 )
EXIT
}
DEFAULT {
GOSUB(KILLSOUND)
SYS("aplay fl/Snake_golpe.wav </dev/null >/dev/null 2>&1")
LET( quit := 1 )
}
SELEND
RET
DEF-FUN( LEVELCLEAR )
GOSUB(KILLSOUND)
GOSUB( level clear )
SYS("aplay fl/Snake_level_clear.wav </dev/null >/dev/null 2>&1")
SLEEP(1)
WHEN( OR( EQ?(LEVEL,3), EQ?(LEVEL,6))){
WHEN( EQ?(Consumed stage,20)){
GOSUB( show )
SYS( "aplay fl/Snake_bono.wav </dev/null >/dev/null 2>&1 &" )
GOSUB(GANA BONO),SLEEP(3)
ADD(SCORE,1000), MOVE-TO(SCORE)
{SCORE, 4, COLUMNA_PRINT} GOSUB( put score )
}
}
GOSUB( another play )
RET
DEF-FUN( another play )
++sound index, WHEN( EQ?(sound index,6)){ LET(sound index := 2) }
back tiles+=2, WHEN( EQ?(back tiles,15)){ LET(back tiles:=1) }
[back tiles] GET(head back tiles), MOVE-TO(tile1)
[PLUS-ONE(back tiles)] GET(head back tiles), MOVE-TO(tile2)
++LEVEL, WHEN( GT?(LEVEL,7)){ LET(LEVEL:=1) }
++ REAL LEVEL
COLOR-FG(15),LOCATE(26,COLUMNA_PRINT) SET-ITALIC, PRN("LEVEL: ",REAL LEVEL,"\OFF")
LET( len := [nHead] GET(board) )
[1:Size] MEM(1) CPUT(board)
CLR-INTERVAL
MEM(len), GOSUB( start )
GOSUB( show )
COND( OR( EQ?(LEVEL,3), EQ?(LEVEL,6)))
GOSUB(challenge stage)
ELS
GOSUB( ready )
CEND
SLEEP(2)
SYS( CAT(CAT("aplay fl/",[sound index]CGET(sounds)), "</dev/null >/dev/null 2>&1 &"))
LET( TIME := TOTAL_TIME )
{TIME, 12, COLUMNA_PRINT} GOSUB( put time )
RET
// put a piece of food at random empty position
DEF-FUN( plant )
SET(r, 0)
LOOP( search position )
LET( r := MOD( CEIL(RAND(MINUS-ONE(Size))), Size ) )
BACK-IF-NOT-EQ( [r] GET(board), SPACE, search position)
[r] {FOOD} CPUT( board )
RET
DEF-FUN( put titles )
LOCATE(2,COLUMNA_PRINT) PRN("\033[38;5;15mSCORE\OFF")
{SCORE, 4, COLUMNA_PRINT} GOSUB( put score )
LOCATE(10,COLUMNA_PRINT) PRN("\033[38;5;11mTIME\OFF")
{TIME, 12, COLUMNA_PRINT} GOSUB( put time )
COLOR-FG(15)
LOCATE(26,COLUMNA_PRINT) SET-ITALIC, PRN("LEVEL: ",REAL LEVEL,"\OFF")
LOCATE(27,COLUMNA_PRINT) SET-INVERSE, PRN("LIVES: ",lives,"\OFF")
RET
DEF-FUN( put time, B, posx, posy )
MSET( i,j,x )
MSET( sb, lsb,nB, p4 )
SET( k,1 )
LOCATE (posx, posy) FILL-BOX(" ",5,20)
LET( sb := STR(B) )
LET( lsb := LEN(sb) )
SET( rx, posy )
LET( p4 := ADD( posx, 4 ))
COLOR-FG(11)
PERF-UP(k, lsb, 1)
LET( nB := VAL( MID( 1, k, sb )) )
SET(x, 1), SET( i, posx )
FOR( LE?(i, p4), ++i )
SET( j, rx )
FOR( LE?(j, ADD( rx, 2 ) ), ++j )
LOCATE(i, j) PRN( STR-TO-UTF8(CHAR( [ PLUS-ONE(nB), x] CGET(big number) MUL-BY(219) )))
++x
NEXT
NEXT
rx += 4
NEXT
PRN("\OFF")
RET
DEF-FUN( put score, SCORE, posx, posy )
MSET( ln,s, sp )
LET( sp := STR( SCORE ))
LET( ln := LEN(sp))
LOCATE ( posx, posy ) FILL-BOX(" ",4,20)
SET(i, 1)
COLOR-FG(15)
PERF-UP( i, ln, 1)
LET( s := VAL( MID( 1, i, sp )) )
[ PLUS-ONE(s) ]
LOCATE( posx, posy ), PRN ( STR-TO-UTF8( GET(numL1) ))
LOC-ROW( PLUS-ONE(posx) ), PRN ( STR-TO-UTF8( GET(numL2) ))
LOC-ROW( PLUS-TWO(posx) ), PRN ( STR-TO-UTF8( GET(numL3) ))
posy += 2
NEXT
PRN("\OFF")
COND( swExtra1 )
COND( GE?(SCORE,5000) )
++lives
FALSE(swExtra1)
SYS( "aplay fl/Snake_bono.wav </dev/null >/dev/null 2>&1 &" )
LOCATE(27,COLUMNA_PRINT) SET-INVERSE, PRN("LIVES: ",lives,"\OFF")
CEND
ELS-COND(swExtra2)
COND( GE?(SCORE,10000) )
++lives
FALSE(swExtra2)
SYS( "aplay fl/Snake_bono.wav </dev/null >/dev/null 2>&1 &" )
LOCATE(27,COLUMNA_PRINT) SET-INVERSE, PRN("LIVES: ",lives,"\OFF")
CEND
CEND
RET
DEF-FUN( set score )
{"┌┐"," ┐","┌┐","┌┐","┐┐","┌┐","┌┐","┌┐","┌┐","┌┐"} APND-LST(numL1)
{"││"," │","┌┘"," ┤","└┤","└┐","├┐"," │","├┤","└┤"} APND-LST(numL2)
{"└┘"," ┴","└┘","└┘"," ┘","└┘","└┘"," ┴","└┘","└┘"} APND-LST(numL3)
{1,1,1,1,0,1,1,0,1,1,0,1,1,1,1} APND-ROW( big number )
{1,1,0,0,1,0,0,1,0,0,1,0,1,1,1} APND-ROW( big number )
{1,1,1,0,0,1,1,1,1,1,0,0,1,1,1} APND-ROW( big number )
{1,1,1,0,0,1,0,1,1,0,0,1,1,1,1} APND-ROW( big number )
{1,0,1,1,0,1,1,1,1,0,0,1,0,0,1} APND-ROW( big number )
{1,1,1,1,0,0,1,1,1,0,0,1,1,1,1} APND-ROW( big number )
{1,0,0,1,0,0,1,1,1,1,0,1,1,1,1} APND-ROW( big number )
{1,1,1,0,0,1,0,0,1,0,0,1,0,0,1} APND-ROW( big number )
{1,1,1,1,0,1,1,1,1,1,0,1,1,1,1} APND-ROW( big number )
{1,1,1,1,0,1,1,1,1,0,0,1,0,0,1} APND-ROW( big number )
RET
DEF-FUN( ready )
{"\033[38;5;4m\033[48;5;11m"}
LOC-COL(16)
LOC-ROW(13); PRN( STR-TO-UTF8(" ▄ ▄▄ ▄ ▄▄ ▄ ▄ "))
LOC-ROW(14); PRN( STR-TO-UTF8(" █▄▀ █▀ █▄█ █ █ ▀▄▀ "))
LOC-ROW(15); PRN( STR-TO-UTF8(" ▀ ▀ ▀▀ ▀ ▀ ▀▄▀ ▀ "))
PRN("\OFF")
RET
DEF-FUN( level clear )
{"\033[38;5;4m\033[48;5;11m"}
LOC-COL(17)
LOC-ROW(12); PRN( STR-TO-UTF8( " ▄ ▄▄ ▄ ▄ ▄▄ ▄ "))
LOC-ROW(13); PRN( STR-TO-UTF8( " █ █▀ █ █ █▀ █ "))
LOC-ROW(14); PRN( STR-TO-UTF8( " ▀ ▀▀ ▀ ▀▀ ▀ "))
LOC-ROW(15); PRN( STR-TO-UTF8( " ▄ ▄ ▄▄ ▄ ▄ "))
LOC-ROW(16); PRN( STR-TO-UTF8( " █ █ █▀ █▄█ █▄▀ "))
LOC-ROW(17); PRN( STR-TO-UTF8( " ▀ ▀ ▀▀ ▀ ▀ ▀ ▀ "))
PRN("\OFF")
RET
DEF-FUN( challenge stage )
{"\033[38;5;4m\033[48;5;11m"}
LOC-COL(9)
LOC-ROW(12); PRN( STR-TO-UTF8( " ▄ ▄ ▄ ▄ ▄ ▄ ▄▄ ▄ ▄▄ ▄▄ "))
LOC-ROW(13); PRN( STR-TO-UTF8( " █ █▄█ █▄█ █ █ █▀ █ █ █ ▄ █▀ "))
LOC-ROW(14); PRN( STR-TO-UTF8( " ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀▀ ▀ ▀ ▀▀ ▀▀ "))
LOC-ROW(15); PRN( STR-TO-UTF8( " ▄ ▄ ▄ ▄ ▄▄ ▄▄ "))
LOC-ROW(16); PRN( STR-TO-UTF8( " ▀▄ █ █▄█ █ ▄ █▀ "))
LOC-ROW(17); PRN( STR-TO-UTF8( " ▀ ▀ ▀ ▀ ▀▀ ▀▀ "))
PRN("\OFF")
RET
DEF-FUN( GANA BONO )
{"\033[38;5;11m\033[48;5;196m"}
LOC-COL(17)
LOC-ROW(12); PRN( STR-TO-UTF8(" ▄ ▄ ▄ ▄ ▄ ▄ "))
LOC-ROW(13); PRN( STR-TO-UTF8(" █▄▀ █ █ █ █ █ █ ▀▄ "))
LOC-ROW(14); PRN( STR-TO-UTF8(" ▀▄▀ ▀ ▀ ▀ ▀ ▀ "))
LOC-ROW(15); PRN( STR-TO-UTF8(" ▄ ▄ ▄ ▄ "))
LOC-ROW(16); PRN( STR-TO-UTF8(" █ █ █ █ █ █ █ "))
LOC-ROW(17); PRN( STR-TO-UTF8(" █ ▄ ▀▄▀ ▀▄▀ ▀▄▀ "))
PRN("\OFF")
RET
DEF-FUN( game over )
{"\033[38;5;15m\033[48;5;9m"}
LOC-COL(17)
LOC-ROW(12); PRN( STR-TO-UTF8(" ▄▄ ▄ ▄ ▄ ▄▄ "))
LOC-ROW(13); PRN( STR-TO-UTF8(" █ ▄ █▄█ █ █ █ █▀ "))
LOC-ROW(14); PRN( STR-TO-UTF8(" ▀▀ ▀ ▀ ▀ ▀ ▀ ▀▀ "))
LOC-ROW(15); PRN( STR-TO-UTF8(" ▄ ▄ ▄ ▄▄ ▄ "))
LOC-ROW(16); PRN( STR-TO-UTF8(" █ █ █ █ █▀ █▄▀ "))
LOC-ROW(17); PRN( STR-TO-UTF8(" ▀ ▀ ▀▀ ▀ ▀ "))
PRN("\OFF")
RET
DEF-FUN( titles )
#define COLOR_INI 232
#define COLOR_FIN 255
SET(k,1)
PERF-UP(k,2,1)
SET(j,COLOR_INI), SET(jbg,COLOR_FIN)
PERF-UP(j,COLOR_FIN,1)
COLOR(j, jbg--)
LOC-COL(17)
LOC-ROW(12); PRN(STR-TO-UTF8(" "))
LOC-ROW(13); PRN(STR-TO-UTF8(" ▄ ▄ ▄ ▄ ▄ ▄▄ "))
LOC-ROW(14); PRN(STR-TO-UTF8(" ▀▄ █ █ █▄█ █▄ █▀ "))
LOC-ROW(15); PRN(STR-TO-UTF8(" ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀▀ "))
LOC-ROW(16); PRN(STR-TO-UTF8(" ▄ ▄ ▄▄ ▄▄ ▄▄ "))
LOC-ROW(17); PRN(STR-TO-UTF8(" ▀▄ █▄█ █▀ █▀ █ █ "))
LOC-ROW(18); PRN(STR-TO-UTF8(" ▀ ▀ ▀▀ ▀▀ ▀▄▀ "))
LOC-ROW(19); PRN(STR-TO-UTF8(" "))
MICROSECS(20000)
NEXT
SET(j,COLOR_FIN), SET(jbg,COLOR_INI)
PERF-DOWN(j,COLOR_INI,1)
COLOR(j, jbg++)
LOC-COL(17)
LOC-ROW(12); PRN(STR-TO-UTF8(" "))
LOC-ROW(13); PRN(STR-TO-UTF8(" ▄ ▄ ▄ ▄ ▄ ▄▄ "))
LOC-ROW(14); PRN(STR-TO-UTF8(" ▀▄ █ █ █▄█ █▄ █▀ "))
LOC-ROW(15); PRN(STR-TO-UTF8(" ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀▀ "))
LOC-ROW(16); PRN(STR-TO-UTF8(" ▄ ▄ ▄▄ ▄▄ ▄▄ "))
LOC-ROW(17); PRN(STR-TO-UTF8(" ▀▄ █▄█ █▀ █▀ █ █ "))
LOC-ROW(18); PRN(STR-TO-UTF8(" ▀ ▀ ▀▀ ▀▀ ▀▄▀ "))
LOC-ROW(19); PRN(STR-TO-UTF8(" "))
MICROSECS(20000)
NEXT
NEXT
PRN("\OFF")
RET
DEF-FUN( prepare maze )
REPL(12,ADD(MUL(w, 3),20),"333333333333",Maze), MOVE-TO(Maze)
REPL(12,ADD(MUL(w, 24),20),"333333333333",Maze), MOVE-TO(Maze)
REPL(12,ADD(MUL(w, 6),7),"333333333333",Maze), MOVE-TO(Maze)
REPL(12,ADD(MUL(w, 6),33),"333333333333",Maze), MOVE-TO(Maze)
REPL(12,ADD(MUL(w, 21),7),"333333333333",Maze), MOVE-TO(Maze)
REPL(12,ADD(MUL(w, 21),33),"333333333333",Maze), MOVE-TO(Maze)
SET(i,7)
PERF-UP(i,10,1)
REPL(1,ADD(MUL(w, i),7),"3",Maze), MOVE-TO(Maze)
REPL(1,ADD(MUL(w, i),44),"3",Maze), MOVE-TO(Maze)
REPL(1,ADD(MUL(w, ADD(i,10)),44),"3",Maze), MOVE-TO(Maze)
REPL(1,ADD(MUL(w, ADD(i,10)),7),"3",Maze), MOVE-TO(Maze)
NEXT
RET
- Output:
AutoHotkey
gosub Init
Gui, +AlwaysOnTop
Gui, font, s12, consolas
Gui, add, Edit, vEditGrid x10 y10 ReadOnly, % grid2Text(oGrid)
Gui, add, Text, vScore x10 y+10 w200 ReadOnly, % "Your Score = " Score
Gui, show,, Snake
GuiControl, Focus, Score
return
;-----------------------------------------
Init:
Width := 100, Height := 30 ; set grid size
tail := 20, step := 10
Timer := 75
item := 0, direction := ""
oTrail := [], Score := 0
xx := generateGrid(width, Height)
oGrid := xx.1
row := xx.2, col := xx.3
return
;-----------------------------------------
Move:
if !item
{
loop
{
Random, itemC, 3, % width-2
Random, itemR, 3, % Height-2
}
until, !oGrid[itemR, itemC]
oGrid[itemR, itemC] := "@"
item := true
}
gosub, crawl
return
;-----------------------------------------
#IfWinActive, Snake
left::
Right::
up::
Down::
if ((A_ThisHotkey = "right") && (Direction = "left"))
|| ((A_ThisHotkey = "left") && (Direction = "right"))
|| ((A_ThisHotkey = "up") && (Direction = "Down"))
|| ((A_ThisHotkey = "Down") && (Direction = "up"))
|| ((A_ThisHotkey = "right") && (Direction = "right"))
|| ((A_ThisHotkey = "left") && (Direction = "left"))
|| ((A_ThisHotkey = "up") && (Direction = "up"))
|| ((A_ThisHotkey = "Down") && (Direction = "Down"))
return
Direction := A_ThisHotkey
gosub, crawl
return
#IfWinActive
;-----------------------------------------
crawl:
switch, Direction
{
case "left" : oGrid[row , col--] := 1
case "Right" : oGrid[row , col++] := 1
case "up" : oGrid[row--, col ] := 1
case "Down" : oGrid[row++, col ] := 1
}
; out of bounds or snake eats itself
if oGrid[row, col] = 1 || col < 1 || col > width || row < 1 || row > height
gosub, YouLose
; snake eats item
if (oGrid[row, col] = "@")
{
item := false
tail += step
GuiControl,, Score, % "Your Score = " ++Score
}
; snake advances
oGrid[row, col] := 2
oTrail.Push(row "," col)
if (oTrail.count() >= tail)
x := StrSplit(oTrail.RemoveAt(1), ","), oGrid[x.1, x.2] := false
GuiControl,, EditGrid, % grid2Text(oGrid)
SetTimer, Move, % 0-Timer
return
;-----------------------------------------
YouLose:
SetTimer, Move, Off
MsgBox, 262180, ,% "Your Score is " Score "`nPlay Again?"
IfMsgBox, No
ExitApp
gosub Init
GuiControl,, EditGrid, % grid2Text(oGrid)
return
;-----------------------------------------
grid2Text(oGrid){
for row, obj in oGrid {
for col, val in obj ; @=item, 2=Head, 1=tail
text .= val = "@" ? "@" : val =2 ? "█" : val = 1 ? "▓" : " "
text .= "`n"
}
return trim(text, "`n")
}
;-----------------------------------------
generateGrid(width, Height){
global oTrail
oGrid := []
loop, % width
{
col := A_Index
loop, % Height
row := A_Index, oGrid[row, col] := false
}
Random, col, 3, % width-2
Random, row, 3, % Height-2
oGrid[row, col] := 2
oTrail.Push(row "," col)
return [oGrid, row, col]
}
;-----------------------------------------
BASIC
Applesoft BASIC
Rather than using DIM X%(1599),Y%(1599) which takes a long time to initialize, instead to speed this up, the program will POKE and PEEK the snake coordinates in memory. On initialization, LOMEM: 24576 moves variables out of the way.
Continue playing by typing the CONT command from the Applesoft BASIC prompt if you quit or it's game over.
REM === VARIABLES ===
REM
REM D(4) X DELTAS
REM E(4) Y DELTAS
REM K(255) KEYCODE BEHESTS
REM FN X X COORDINATES OF SNAKE FROM MEMORY
REM FN Y Y COORDINATES OF SNAKE FROM MEMORY
REM A <UNUSED>
REM B BEHEST: 0=NOTHING 1=RIGHT 2=LEFT 3=DOWN 4=UP 5=QUIT
REM C SCRN COLOR
REM D X-DELTA
REM E Y-DELTA
REM F FOOD COLOR= 4 DARK GREEN
REM H HIT= 127
REM I KEYBOARD= 49152
REM J CLEAR KEYBOARD= 49168
REM K KEYCODE
REM L LENGTH WIDTH&HEIGHT= 40
REM M MAX= 1599 UPPER BOUND OF SNAKE X AND Y COORDINATES
REM N NEW POSITION OF HEAD OF SNAKE
REM O OLD POSITION OF TAIL OF SNAKE
REM P ADDRESS OF 1600 X COORDINATES OF SNAKE
REM Q QUIT= 5
REM R REMAINING COLOR= 9 ORANGE
REM S SNAKE HEAD COLOR= 13 YELLOW
REM T ONE= 1
REM U X FOOD COORDINATE
REM V Y FOOD COORDINATE
REM W WALL COLOR= 10 GREY
REM X X SNAKE HEAD COORDINATE
REM Y Y SNAKE HEAD COORDINATE
REM Z ADDRESS OF 1600 Y COORDINATES OF SNAKE
REM Q$ QUIT MESSAGE
REM === KEYBOARD ===
REM
REM UPPER LOWER KEY BEHEST
REM 155 ESC QUIT
REM 139 UP UP
REM 193 225 A UP
REM 201 233 I UP
REM 138 DOWN DOWN
REM 218 250 Z DOWN
REM 203 235 K DOWN
REM 136 LEFT LEFT
REM 202 234 J LEFT
REM 149 RIGHT RIGHT
REM 204 236 L RIGHT
0 ON B = Q GOTO 5: IF B THEN D = D(B):E = E(B)
1 X = X + D:Y = Y + E:C = SCRN( X,Y): COLOR= S: PLOT X,Y: COLOR= R: PLOT FN X(N), FN Y(N):N = N - T + (N = 0) * M: POKE P + N,X: POKE Z + N,Y: ON C(C) GOTO 4: IF NOT C THEN COLOR= 0: PLOT FN X(O), FN Y(O):O = O - T + (O = 0) * M
2 IF C THEN U = INT ( RND (T) * L):V = INT ( RND (T) * L): ON SCRN( U,V) > 0 GOTO 2: COLOR= F: PLOT U,V
3 K = PEEK (I):B = PEEK (J * (K > H)):B = K(K): GOTO
4 COLOR= T: PLOT X,Y: READ Q$: DATA5,20,20,1,-1,1,-1,13,9,4,10,1,40,1599,127,49152,49168,4608,6400,5,4,4,4,4,4,3,3,3,3,3,2,2,2,1,1,1,DONE,GAME OVER
5 IF Q = 5 THEN PRINT Q$;: END : HOME :Q = Q * (B = Q):B = 0: ON Q = 5 GOTO : RUN
6 LOMEM: 24576
7 DIM K(255),D(4),E(4),C(15): READ Q,X,Y,D(1),D(2),E(3),E(4),S,R,F,W,T,L,M,H,I,J,P,Z,K(155),K(139),K(193),K(225),K(201),K(233),K(138),K(218),K(250),K(203),K(235),K(136),K(202),K(234),K(149),K(204),K(236),Q$
8 DEF FN X(I) = PEEK (P + I): DEF FN Y(I) = PEEK (Z + I):B = RND (0):C(S) = T:C(R) = T:C(W) = T:B = INT ( RND (T) * 4) + T:D = D(B):E = E(B): POKE P + O,X: POKE Z + O,Y
9 GR : HOME : COLOR= W: VLIN 0,39 AT 0: VLIN 0,39 AT 39: HLIN 1,38 AT 0: HLIN 1,38 AT 39: COLOR= F: PLOT X + D,Y + E: GOTO
Craft Basic
![](http://static.miraheze.org/rosettacodewiki/2/2e/Craft_Basic_Snake.png)
rem Snake example game for Craft Basic
rem by Gemino Smothers 2022
rem www.lucidapogee.com
title "Snake!"
define gfxx = 330, gfxy = 296
define upkey = 0, rightkey = 0, downkey = 0, leftkey = 0, esckey = 0
define speed = 0, delay = 0, score = 0, game = 1
define maxsize = 1000, size = 9, direction = int(rnd * 4) + 1
define rx = int(rnd * (gfxx - 24)) + 12
define ry = int(rnd * (gfxy - 40)) + 25
dim sx[maxsize]
dim sy[maxsize]
let sx[0] = gfxx / 2
let sy[0] = gfxy / 2
fill on
bgcolor 128, 64, 0
cls graphics
resize 0, 0, gfxx + 10, gfxy + 56
center
formid 1
staticform 1, 1, 100, 14
fgcolor 255, 255, 0
bgcolor 0, 80, 0
colorform
alert "Snake! by Gemino Smothers 2022"
alert "Get the gray little rodents and avoid hitting yourself or walls."
alert "Use arrow keys to move. Esc to exit."
input "Enter game speed between 0 to 100+", speed
fgcolor 0, 80, 0
rect 0, 0, gfxx, gfxy
do
button upkey, 38
button rightkey, 39
button downkey, 40
button leftkey, 37
button esckey, 27
if upkey = 1 and direction <> 3 then
let direction = 1
endif
if rightkey = 1 and direction <> 4 then
let direction = 2
endif
if downkey = 1 and direction <> 1 then
let direction = 3
endif
if leftkey = 1 and direction <> 2 then
let direction = 4
endif
fgcolor 0, 80, 0
oval sx[size], sy[size], 15, 15
let i = size + 1
do
let i = i - 1
let c = i - 1
if sx[0] = sx[i] and sy[0] = sy[i] = 1 then
let game = 0
endif
let sx[i] = sx[c]
let sy[i] = sy[c]
fgcolor 0, 255, 0
oval sx[i], sy[i], 15, 15
loop i > 1
fgcolor 0, 0, 255
oval sx[0] + 5, sy[0] + 5, 3, 3
oval sx[0] + 9, sy[0] + 5, 3, 3
if direction = 1 then
let sy[0] = sy[0] - 15
endif
if direction = 2 then
let sx[0] = sx[0] + 15
endif
if direction = 3 then
let sy[0] = sy[0] + 15
endif
if direction = 4 then
let sx[0] = sx[0] - 15
endif
if sx[0] <= -10 or sx[0] >= gfxx or sy[0] <= -10 or sy[0] >= gfxy = 1 then
let game = 0
endif
if sx[0] + 15 >= rx and sx[0] <= rx + 15 and sy[0] + 15 >= ry and sy[0] <= ry + 15 = 1 then
playwave "examples\tada.wav"
fgcolor 0, 80, 0
rect 0, 0, gfxx, gfxy
let rx = int(rnd * (gfxx - 24)) + 12
let ry = int(rnd * (gfxy - 40)) + 25
let size = size + 3
let score = score + 1
endif
fgcolor 100,100,100
oval rx, ry, 15, 15
fgcolor 255, 0, 0
oval rx + 5, ry + 5, 3, 3
oval rx + 9, ry + 5, 3, 3
fgcolor 255, 255, 0
formid 1
formtext "Score: ", score
updateform
let delay = clock
do
wait
loop clock < delay + speed
loop esckey <> 1 and game = 1
playwave "examples\boom.wav"
alert "Game over! Score: ", score
FreeBASIC
REM Snake
#define EXTCHAR Chr(255)
Dim Shared As Integer puntos, contar, longitud, posX, posY, oldhi = 0
Dim Shared As Integer x(500), y(500)
For contar = 1 To 500
x(contar) = 0 : y(contar) = 0
Next contar
Dim Shared As Byte fruitX, fruitY, convida
Dim Shared As Single delay
Dim Shared As String direccion, usuario
Sub Intro
Cls
Color 15, 0
Print " _ _ _ _ "
Print " / \ / \ / \ / \ "
Print " ___________/ _\/ _\/ _\/ _\____________ "
Print " __________/ /_/ /_/ /_/ /______________ "
Print " | /\ /\ /\ / \ \___ "
Print " |/ \_/ \_/ \_/ \ "+Chr(248)+"\ "
Print " \___/--< "
Color 14, 0
Locate 10, 28: Print "---SNAKE---"
Locate 12, 4: Print "Para jugar, usa las teclas de flecha para moverte."
Locate 13, 4: Print "Pulsa <1-3> para velocidad, o <Esc> para salir."
If puntos > oldhi Then oldhi = puntos
Locate 14, 4: Print "M xima puntuaci¢n: "; oldhi
usuario = ""
While usuario = ""
usuario = Inkey
Wend
If usuario = Chr(27) Then End 'ESC
delay = .14
If usuario = "1" Then delay = .26
If usuario = "3" Then delay = .08
Cls
longitud = 9
puntos = 0
posX = 40 : posY = 10
direccion = "derecha"
convida = true
fruitX = Int(Rnd * 79) + 1
fruitY = Int(Rnd * 20) + 1
Locate 22, 1
For contar = 1 To 80
Print Chr(196); ''"-";
Next contar
End Sub
Sub MenuPrincipal
Dim As Integer num, oldX, oldY
Dim As Single tm, tm2
Color 10
Locate 23, 2: Print "SNAKE - <Esc> salir - Puntos: "; puntos
If posX = fruitX And posY = fruitY Then
longitud += 1
puntos += 1
fruitX = Int(Rnd * 79) + 1
fruitY = Int(Rnd * 21) + 1
End If
Locate fruitY, fruitX : Color 12: Print Chr(01) : Color 10'"@"
x(0) = posX : y(0) = posY
For contar = 1 To 499
num = 500 - contar
x(num) = x(num - 1)
y(num) = y(num - 1)
Next contar
oldX = x(longitud) : oldY = y(longitud)
If oldX > 0 And oldY > 0 Then Locate oldY, oldX : Print " "
Locate posY, posX: Print Chr(219) '"Û"
tm = Timer
tm2 = tm + delay
While tm < tm2
tm = Timer
usuario = Inkey
If usuario = EXTCHAR & "H" Then direccion = "arriba"
If usuario = EXTCHAR & "P" Then direccion = "abajo"
If usuario = EXTCHAR & "K" Then direccion = "izquierda"
If usuario = EXTCHAR & "M" Then direccion = "derecha"
If usuario = Chr(27) Then Intro
Wend
If direccion = "derecha" Then posX += 1
If posX > 80 Then convida = false
If direccion = "izquierda" Then posX -= 1
If posX < 1 Then convida = false
If direccion = "arriba" Then posY -= 1
If posY < 1 Then convida = false
If direccion = "abajo" Then posY += 1
If posY > 21 Then convida = false
For contar = 0 To longitud
If posX = x(contar) And posY = y(contar) Then convida = false
Next contar
If convida = false Then
Cls : Locate 11, 19: Print "Pulsa <space>..."
Locate 10, 18: Print "Has muerto! Con"; puntos; " puntos." : Sleep
While Inkey = "": Wend
Intro
End If
MenuPrincipal
End Sub
'--- Programa Principal ---
Randomize Timer
Intro
MenuPrincipal
End
'--------------------------
Integer BASIC
0 IF B=Q THEN GOTO 9: IF B THEN D=D(B): IF B THEN E=E(B):X=X+D:Y=Y+E:A= SCRN(X,Y): COLOR=S: PLOT X,Y: COLOR=R: PLOT X(N),Y(N)
1 N=N-T+(N=1)*M:X(N)=X:Y(N)=Y: IF C(A) THEN GOTO 9: IF A THEN GOTO 2: COLOR=0: PLOT X(O),Y(O):O=O-T+(O=1)*M: GOTO 3
2 U= RND (L):V= RND (L): IF SCRN(U,V)>0 THEN GOTO 2: COLOR=F: PLOT U,V
3 K= PEEK (I):B= PEEK (J*(K>H)):B=K(K): GOTO 0
4 DIM X(1600),Y(1600),K(255),D(4),E(4),C(15): FOR I=0 TO 255:K(I)=0: NEXT I: FOR I=1 TO 4:D(I)=0:E(I)=0: NEXT I
5 FOR I=0 TO 15:C(I)=0: NEXT I:Q=5:X=20:Y=20:E(1)=-1:D(2)=-1:E(3)=1:D(4)=1:S=13:R=9:F=4:W=10:T=1:L=40:M=1600
6 H=127:I=-16384:J=-16368:K(155)=5:K(193)=1:K(225)=1:K(201)=1:K(233)=1:K(136)=2:K(202)=2:K(234)=2:N=1:O=1
7 K(218)=3:K(250)=3:K(203)=3:K(235)=3:K(149)=4:K(204)=4:K(236)=4:C(S)=T:C(R)=T:C(W)=T:B= RND (4)+T:D=D(B)
8 E=E(B):X(O)=X:Y(O)=Y: GR : CALL -936: COLOR=W: VLIN 0,39 AT 0: VLIN 0,39 AT 39: HLIN 1,38 AT 0: HLIN 1,38 AT 39: COLOR=F: PLOT X+D,Y+E: GOTO 0
9 IF Q#5 THEN GOTO 4:Q=Q*(B=Q): COLOR=T: IF Q=0 THEN PLOT X,Y: IF Q=0 THEN PRINT "GAME OVER";: IF Q THEN PRINT "DONE";:K= PEEK (J):B=0: END
Locomotive Basic
![](http://static.miraheze.org/rosettacodewiki/thumb/6/68/Snake_locomotive_basic.png/300px-Snake_locomotive_basic.png)
Use the arrow keys to control movement direction. If you find gameplay too easy, lower the skill parameter in line 20 or increase ml (maximum snake length).
If you would like to play this in CPCBasic, paste the code into the box at the top in CPCBasic, then click on the "Reset" and "Run" buttons below and finally click on the CPC screen so it gets keyboard focus. (This will turn the frame around the screen dark).
10 mode 1:randomize time
15 ink 0,0:ink 1,6:ink 2,18:ink 3,11
20 sx=20:sy=5:dx=1:dy=0:ml=20:dim ox(ml),oy(ml):oi=1:ll=4:skill=8
30 f$=chr$(228):w$=chr$(127):b$=chr$(231)
40 pen 3:print string$(40,w$);
50 for i=2 to 20:print w$;space$(38);w$;:next
60 print string$(40,w$);
70 locate 11, 11:print string$(20,w$);
80 gosub 260
140 locate sx,sy:pen 2: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 2,62500/1000,10: sound 2,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
211 if inkey(1)>-1 then dx=1:dy=0
212 if inkey(8)>-1 then dx=-1:dy=0
213 if inkey(0)>-1 then dx=0:dy=-1
214 if inkey(2)>-1 then dx=0:dy=1
215 next
220 locate sx,sy:pen 2: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 140
260 fx=rnd*39+1:fy=rnd*19+1
270 locate fx,fy:a$=copychr$(#0)
280 if a$<>" " then 260
290 pen 1:print f$;
300 return
ZX Spectrum Basic
By ancient tradition, the controls are Q for up, A for down, O for left, and P for right.
A screenshot is here.
Note that lines 10
to 210
and 580
to 890
—more than half the program—define graphics characters for the snake's head (facing in different directions) and for its food. If you're happy to make do with characters from the standard character set, you can easily adapt lines 220
to 570
to work on their own. The things the snake eats are supposed to be apples, although they don't look too much like them.
10 FOR i=0 TO 7
20 READ bits
30 POKE USR "L"+i,bits
40 NEXT i
50 FOR i=0 TO 7
60 READ bits
70 POKE USR "R"+i,bits
80 NEXT i
90 FOR i=0 TO 7
100 READ bits
110 POKE USR "P"+i,bits
120 NEXT i
130 RESTORE 740
140 FOR i=7 TO 0 STEP -1
150 READ bits
160 POKE USR "D"+i,bits
170 NEXT i
180 FOR i=0 TO 7
190 READ bits
200 POKE USR "F"+i, bits
210 NEXT i
220 PAPER 0
230 CLS
240 LET snakex=19
250 LET snakey=15
260 LET dx=-1
270 LET dy=0
280 LET s$=CHR$ 15+CHR$ 20+CHR$ 15+CHR$ 21
290 LET foodx=INT (RND*32)
300 LET foody=INT (RND*22)
310 IF SCREEN$ (foody,foodx)<>" " THEN GO TO 290
320 INK 2
330 PRINT AT foody,foodx;CHR$ 149
340 INK 4
350 INVERSE 1
360 PRINT AT CODE s$,CODE s$(1);"#"
370 INVERSE 0
380 IF INKEY$="q" AND dy=0 THEN LET dx=0: LET dy=-1
390 IF INKEY$="a" AND dy=0 THEN LET dx=0: LET dy=1
400 IF INKEY$="o" AND dx=0 THEN LET dx=-1: LET dy=0
410 IF INKEY$="p" AND dx=0 THEN LET dx=1: LET dy=0
420 IF dx=-1 THEN PRINT AT snakey,snakex;CHR$ 155
430 IF dx=1 THEN PRINT AT snakey,snakex;CHR$ 161
440 IF dy=1 THEN PRINT AT snakey,snakex;CHR$ 159
450 IF dy=-1 THEN PRINT AT snakey,snakex;CHR$ 147
460 LET s$=CHR$ snakey+CHR$ snakex+s$
470 IF snakex=foodx AND snakey=foody THEN GO TO 290
480 PRINT AT CODE s$(LEN s$-1),CODE s$(LEN s$);" "
490 LET s$=s$( TO LEN s$-2)
500 LET snakex=snakex+dx
510 LET snakey=snakey+dy
520 IF snakex=-1 THEN LET snakex=31
530 IF snakex=32 THEN LET snakex=0
540 IF snakey=-1 THEN LET snakey=21
550 IF snakey=22 THEN LET snakey=0
560 IF SCREEN$ (snakey,snakex)="#" THEN STOP
570 GO TO 340
580 DATA BIN 00001111
590 DATA BIN 00111111
600 DATA BIN 01110011
610 DATA BIN 11110011
620 DATA BIN 11111111
630 DATA BIN 01111111
640 DATA BIN 00000111
650 DATA BIN 00011111
660 DATA BIN 11110000
670 DATA BIN 11111100
680 DATA BIN 11001110
690 DATA BIN 11001111
700 DATA BIN 11111111
710 DATA BIN 11111110
720 DATA BIN 11100000
730 DATA BIN 11111000
740 DATA BIN 00011000
750 DATA BIN 00111100
760 DATA BIN 01111100
770 DATA BIN 01111101
780 DATA BIN 11001101
790 DATA BIN 11001111
800 DATA BIN 11111111
810 DATA BIN 11111111
820 DATA BIN 00000100
830 DATA BIN 00001000
840 DATA BIN 01101011
850 DATA BIN 11111100
860 DATA BIN 11111100
870 DATA BIN 11111100
880 DATA BIN 01111111
890 DATA BIN 00110110
C
As some implementation below (C++) works on Windows console, let it work on Linux. Other implementations could be added as well, reusing the api.
// Snake
// The problem with implementing this task in C is, the language standard
// does not cover some details essential for interactive games:
// a nonblocking keyboard input, a positional console output,
// a and millisecond-precision timer: these things are all system-dependent.
// Therefore the program is split in two pieces, a system-independent
// game logic, and a system-dependent UI, separated by a tiny API:
char nonblocking_getch();
void positional_putch(int x, int y, char ch);
void millisecond_sleep(int n);
void init_screen();
void update_screen();
void close_screen();
// The implementation of a system-dependent part.
// Requires POSIX IEEE 1003.1-2008 compliant system and ncurses library.
#ifdef __linux__
#define _POSIX_C_SOURCE 200809L
#include <time.h> // nanosleep
#include <ncurses.h> // getch, mvaddch, and others
char nonblocking_getch() { return getch(); }
void positional_putch(int x, int y, char ch) { mvaddch(x, y, ch); }
void millisecond_sleep(int n) {
struct timespec t = { 0, n * 1000000 };
nanosleep(&t, 0);
// for older POSIX standards, consider usleep()
}
void update_screen() { refresh(); }
void init_screen() {
initscr();
noecho();
cbreak();
nodelay(stdscr, TRUE);
}
void close_screen() { endwin(); }
#endif
// An implementation for some other system...
#ifdef _WIN32
#error "not implemented"
#endif
// The game logic, system-independent
#include <time.h> // time
#include <stdlib.h> // rand, srand
#define w 80
#define h 40
int board[w * h];
int head;
enum Dir { N, E, S, W } dir;
int quit;
enum State { SPACE=0, FOOD=1, BORDER=2 };
// negative values denote the snake (a negated time-to-live in given cell)
// reduce a time-to-live, effectively erasing the tail
void age() {
int i;
for(i = 0; i < w * h; ++i)
if(board[i] < 0)
++board[i];
}
// put a piece of food at random empty position
void plant() {
int r;
do
r = rand() % (w * h);
while(board[r] != SPACE);
board[r] = FOOD;
}
// initialize the board, plant a very first food item
void start(void) {
int i;
for(i = 0; i < w; ++i)
board[i] = board[i + (h - 1) * w] = BORDER;
for(i = 0; i < h; ++i)
board[i * w] = board[i * w + w - 1] = BORDER;
head = w * (h - 1 - h % 2) / 2; // screen center for any h
board[head] = -5;
dir = N;
quit = 0;
srand(time(0));
plant();
}
void step() {
int len = board[head];
switch(dir) {
case N: head -= w; break;
case S: head += w; break;
case W: --head; break;
case E: ++head; break;
}
switch(board[head]) {
case SPACE:
board[head] = len - 1; // keep in mind len is negative
age();
break;
case FOOD:
board[head] = len - 1;
plant();
break;
default:
quit = 1;
}
}
void show() {
const char * symbol = " @.";
int i;
for(i = 0; i < w * h; ++i)
positional_putch(i / w, i % w,
board[i] < 0 ? '#' : symbol[board[i]]);
update_screen();
}
int main (int argc, char * argv[]) {
init_screen();
start();
do {
show();
switch(nonblocking_getch()) {
case 'i': dir = N; break;
case 'j': dir = W; break;
case 'k': dir = S; break;
case 'l': dir = E; break;
case 'q': quit = 1; break;
}
step();
millisecond_sleep(100); // beware, this approach
// is not suitable for anything but toy projects like this
}
while(!quit);
millisecond_sleep(999);
close_screen();
return 0;
}
C++
Simple Windows console implementation.
![](http://static.miraheze.org/rosettacodewiki/thumb/6/6c/SnakeCpp.png/200px-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
unit SnakeGame;
interface
uses
Winapi.Windows, System.SysUtils,
System.Classes, Vcl.Graphics, Vcl.Forms, Vcl.Dialogs,
System.Generics.Collections, Vcl.ExtCtrls;
type
TSnakeApp = class(TForm)
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure FormPaint(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure DoFrameStep(Sender: TObject);
procedure Reset;
private
{ Private declarations }
FrameTimer: TTimer;
public
{ Public declarations }
end;
TSnake = class
len: Integer;
alive: Boolean;
pos: TPoint;
posArray: TList<TPoint>;
dir: Byte;
private
function Eat(Fruit: TPoint): Boolean;
function Overlap: Boolean;
procedure update;
public
procedure Paint(Canvas: TCanvas);
procedure Reset;
constructor Create;
destructor Destroy; override;
end;
TFruit = class
FruitTime: Boolean;
pos: TPoint;
constructor Create;
procedure Reset;
procedure Paint(Canvas: TCanvas);
private
procedure SetFruit;
end;
const
L = 1;
R = 2;
D = 4;
U = 8;
var
SnakeApp: TSnakeApp;
block: Integer = 24;
wid: Integer = 30;
hei: Integer = 20;
fruit: TFruit;
snake: TSnake;
implementation
{$R *.dfm}
function Rect(x, y, w, h: Integer): TRect;
begin
Result := TRect.Create(x, y, x + w, y + h);
end;
{ TSnake }
constructor TSnake.Create;
begin
posArray := TList<TPoint>.Create;
Reset;
end;
procedure TSnake.Paint(Canvas: TCanvas);
var
pos: TPoint;
i, l: Integer;
r: TRect;
begin
with Canvas do
begin
Brush.Color := rgb(130, 190, 0);
i := posArray.count - 1;
l := posArray.count;
while True do
begin
pos := posArray[i];
dec(i);
r := rect(pos.x * block, pos.y * block, block, block);
FillRect(r);
dec(l);
if l = 0 then
Break;
end;
end;
end;
procedure TSnake.Reset;
begin
alive := true;
pos := Tpoint.Create(1, 1);
posArray.Clear;
posArray.Add(Tpoint.Create(pos));
len := posArray.Count;
dir := r;
end;
destructor TSnake.Destroy;
begin
posArray.Free;
inherited;
end;
function TSnake.Eat(Fruit: TPoint): Boolean;
begin
result := (pos.X = Fruit.X) and (pos.y = Fruit.y);
if result then
begin
inc(len);
if len > 5000 then
len := 500;
end;
end;
function TSnake.Overlap: Boolean;
var
aLen: Integer;
tp: TPoint;
i: Integer;
begin
aLen := posArray.count - 1;
for i := 0 to aLen - 1 do
begin
tp := posArray[i];
if (tp.x = pos.x) and (tp.y = pos.y) then
Exit(True);
end;
Result := false;
end;
procedure TSnake.update;
begin
if not alive then
exit;
case dir of
l:
begin
dec(pos.X);
if pos.X < 1 then
pos.x := wid - 2
end;
r:
begin
inc(pos.x);
if (pos.x > (wid - 2)) then
pos.x := 1;
end;
U:
begin
dec(pos.y);
if (pos.y < 1) then
pos.y := hei - 2
end;
D:
begin
inc(pos.y);
if (pos.y > hei - 2) then
pos.y := 1;
end;
end;
if Overlap then
alive := False
else
begin
posArray.Add(TPoint(pos));
if len < posArray.Count then
posArray.Delete(0);
end;
end;
{ TFruit }
constructor TFruit.Create;
begin
Reset;
end;
procedure TFruit.Paint(Canvas: TCanvas);
var
r: TRect;
begin
with Canvas do
begin
Brush.Color := rgb(200, 50, 20);
r := Rect(pos.x * block, pos.y * block, block, block);
FillRect(r);
end;
end;
procedure TFruit.Reset;
begin
fruitTime := true;
pos := Tpoint.Create(0, 0);
end;
procedure TFruit.SetFruit;
begin
pos.x := Trunc(Random(wid - 2) + 1);
pos.y := Trunc(Random(hei - 2) + 1);
fruitTime := false;
end;
procedure TSnakeApp.DoFrameStep(Sender: TObject);
begin
Invalidate;
end;
procedure TSnakeApp.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FrameTimer.Free;
snake.Free;
Fruit.Free;
end;
procedure TSnakeApp.FormCreate(Sender: TObject);
begin
Canvas.pen.Style := psClear;
ClientHeight := block * hei;
ClientWidth := block * wid;
DoubleBuffered := True;
KeyPreview := True;
OnClose := FormClose;
OnKeyDown := FormKeyDown;
OnPaint := FormPaint;
snake := TSnake.Create;
Fruit := TFruit.Create();
FrameTimer := TTimer.Create(nil);
FrameTimer.Interval := 250;
FrameTimer.OnTimer := DoFrameStep;
FrameTimer.Enabled := True;
end;
procedure TSnakeApp.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
function ValidDir(value: Byte): Byte;
var
combination: Byte;
begin
combination := (value or snake.dir);
if (combination = 3) or (combination = 12) then
Result := snake.dir
else
Result := value;
end;
begin
case Key of
VK_LEFT:
snake.dir := ValidDir(l);
VK_RIGHT:
snake.dir := ValidDir(r);
VK_UP:
snake.dir := ValidDir(U);
VK_DOWN:
snake.dir := ValidDir(D);
VK_ESCAPE:
Reset;
end;
end;
procedure TSnakeApp.FormPaint(Sender: TObject);
var
i: Integer;
r: TRect;
frameR: Double;
begin
with Canvas do
begin
Brush.Color := rgb(0, $22, 0);
FillRect(ClipRect);
Brush.Color := rgb(20, 50, 120);
for i := 0 to wid - 1 do
begin
r := rect(i * block, 0, block, block);
FillRect(r);
r := rect(i * block, ClientHeight - block, block, block);
FillRect(r);
end;
for i := 1 to hei - 2 do
begin
r := Rect(1, i * block, block, block);
FillRect(r);
r := Rect(ClientWidth - block, i * block, block, block);
FillRect(r);
end;
if (Fruit.fruitTime) then
begin
Fruit.setFruit();
frameR := FrameTimer.Interval * 0.95;
if frameR < 30 then
frameR := 30;
FrameTimer.Interval := trunc(frameR);
end;
Fruit.Paint(Canvas);
snake.update();
if not snake.alive then
begin
FrameTimer.Enabled := False;
Application.ProcessMessages;
ShowMessage('Game over');
Reset;
exit;
end;
if (snake.eat(Fruit.pos)) then
Fruit.fruitTime := true;
snake.Paint(Canvas);
Brush.Style := bsClear;
Font.Color := rgb(200, 200, 200);
Font.Size := 18;
TextOut(50, 0, (snake.len - 1).ToString);
end;
end;
procedure TSnakeApp.Reset;
begin
snake.Reset;
Fruit.Reset;
FrameTimer.Interval := 250;
FrameTimer.Enabled := True;
end;
end.
Form resources:
object SnakeApp: TSnakeApp
OnCreate = FormCreate
end
EasyLang
subr fruit
rx = (randint 20 - 1) * 5 + 2.5
ry = (randint 20 - 1) * 5 + 2.5
.
subr start
fruit
game = 1
sx[] = [ 52.5 0 0 0 0 ]
sy[] = [ 52.5 0 0 0 0 ]
dir = randint 4
timer 0
.
background 242
move 30 70
clear
color 997
text "SNAKE"
textsize 5
move 6 40
text "Keys or mouse for controlling"
move 6 30
text "Space or click to to start"
#
on key
if game = 0 and keybkey = " "
start
return
.
if dir mod 2 = 1
if keybkey = "ArrowRight"
dir = 2
elif keybkey = "ArrowLeft"
dir = 4
.
else
if keybkey = "ArrowUp"
dir = 1
elif keybkey = "ArrowDown"
dir = 3
.
.
.
on mouse_down
if game = 0
start
return
.
if dir mod 2 = 1
if mouse_x < sx
dir = 4
else
dir = 2
.
else
if mouse_y < sy
dir = 3
else
dir = 1
.
.
.
on timer
clear
color 997
move 2 95
text "Score: " & 10 * len sx[] - 50
color 966
move rx ry
circle 1.5
#
sx = sx[1] ; sy = sy[1]
if dir = 1
sy += 5
elif dir = 2
sx += 5
elif dir = 3
sy -= 5
elif dir = 4
sx -= 5
.
if sx < 0 or sx > 100 or sy < 0 or sy > 100
game = 0
.
color 494
for i = len sx[] downto 2
if sx = sx[i] and sy = sy[i]
game = 0
.
sx[i] = sx[i - 1]
sy[i] = sy[i - 1]
if sx[i] > 0
move sx[i] sy[i]
circle 2.5
.
.
move sx sy
circle 2.5
color 000
if dir = 2 or dir = 4
move sx sy + 1
circle 0.5
move sx sy - 1
circle 0.5
else
move sx + 1 sy
circle 0.5
move sx - 1 sy
circle 0.5
.
if sx = rx and sy = ry
len sx[] len sx[] + 3
len sy[] len sy[] + 3
fruit
.
sx[1] = sx ; sy[1] = sy
if game = 1
timer 0.15
else
color 997
move 10 10
text "Space or click new game"
.
.
Emacs Lisp
![](http://static.miraheze.org/rosettacodewiki/thumb/5/59/Snake_emacs_lisp.png/300px-Snake_emacs_lisp.png)
GNU Emacs has a built-in Snake game in Lisp that is started with
M-x snake
and which uses the same tile set as the included Tetris game.
F#
open System
open System.Threading.Tasks
open System.Threading
module SnakeGame =
/// 🚏 Directions on our grid:
type Movement =
| Left of Position
| Right of Position
| Down of Position
| Up of Position
| InvalidMove
/// 🐍 Sort of like a list, but not:
and Snake<'Position> =
| Head of Position * Snake<'Position>
| Belly of Position * Snake<'Position>
| Tail
/// 🕹️ Our basic runtime information:
and Game =
| GameRunning of Movement * Snake<Position> * Grid * Eaten:int * Food
| GameOver of Grid * Eaten:int
/// 🧭 x & y in our plane:
and Position = int * int
/// 🍎 The food our snake will eat:
and Food = Position * string
/// 🌐 A simple two dimensional plane:
and Grid = string[][]
/// Making a list of positions from a Snake 🐍
let snakeUnzip (snake:Snake<Position>) =
let rec unzip snake carry =
match snake with
| Head (p, rest) -> unzip rest <| carry @ [p]
| Belly (p, rest) -> unzip rest <| carry @ [p]
| Tail -> carry
unzip snake []
/// Making a Snake from a list of positions 🐍
let snakeZip (positions:list<Position>) (upto:int) =
let correctLength = (List.take upto positions)
let rec zip (carry:Snake<Position>) (rest:list<Position>) =
match rest with
| head::[] -> zip (Head(head, carry)) []
| back::front -> zip (Belly (back, carry)) front
| [] -> carry
zip Tail (List.rev correctLength)
module Graphics =
let private random = new Random()
let private head = "🤢"
let private belly = "🟢"
let private display = "⬜"
let private errorDisplay = "🟥"
let private food = [|"🐁";"🐀";"🐥";"🪺";"🐸";"🐛";"🪰";"🐞";"🦗"|]
let private randomFood () = food.[random.Next(food.Length - 1)]
let isFood square = Array.contains square food
let isFreeSpace square = square = display || square = errorDisplay
let isOccupied square =
match square with
| square when isFreeSpace square -> false
| square when isFood square -> false
| _ -> true
let makeGrid (dimensionsSquared) : Grid =
let row _ = Array.init dimensionsSquared (fun _ -> display)
Array.init dimensionsSquared row
let clearGrid (grid:Grid) : unit =
Array.iteri (fun i row ->
Array.iteri (fun j _ ->
grid.[i].[j] <- display
) row
) grid
let render (grid:Grid) : unit =
Console.Clear()
Array.iter (fun (row:string array) ->
let prettyprint = String.concat "" row
printfn $"{prettyprint}") grid
printfn "Snake Game in FSharp by @wiredsister"
printfn "Controls: ⬅️ ↕️ ➡️"
printfn "Press Ctrl+C to Quit Game"
Console.Title <- "FSharp Snake 🐍"
let getFreeSpaces (grid:Grid) : list<Position> =
let results : Position list ref = ref []
for i in 0..(grid.Length-1) do
for j in 0..(grid.Length-1) do
if isFreeSpace grid.[i].[j]
then results.Value <- results.Value @ [i,j]
else ()
()
results.Value
let getFood (grid:Grid) : Food =
Console.Beep()
let freeSpaces =
getFreeSpaces grid
|> Array.ofList
let food = randomFood ()
let randomPos = freeSpaces.[random.Next(freeSpaces.Length - 1)]
randomPos, food
let dropFood (grid:Grid) (food:Food) =
let (x, y), animal = food
grid.[x].[y] <- animal
let slither (snake:Snake<Position>) (grid:Grid) : unit =
try
let rec slithering (body:Snake<Position>) =
match body with
| Head(p, s) ->
let row, column = p
grid.[row].[column] <- head
slithering s
| Belly(p, s) ->
let row, column = p
grid.[row].[column] <- belly
slithering s
| Tail -> ()
do slithering snake
with _ -> failwith "ERROR: Could not slither snake!"
let endGame (grid:Grid) : unit =
Console.Clear()
Array.iteri (fun i row ->
Array.iteri (fun j _ ->
grid.[i].[j] <- errorDisplay
) row
) grid
Array.iter (fun (row:string array) ->
let prettyprint = String.concat "" row
printfn $"{prettyprint}") grid
Console.Beep()
module GamePlay =
let moveUp (snake:Snake<Position>) (grid:Grid) (eaten:int) (food:Food) : Game =
match snake with
| Head (p, rest:Snake<Position>) ->
let x, y = p
let shiftUp = ((x-1), y)
try
match shiftUp with
| (row,column) when Graphics.isOccupied grid.[row].[column] ->
GameOver (grid, eaten)
| (row, column) when Graphics.isFood grid.[row].[column] ->
let unzipped = snakeUnzip (Head (shiftUp, (Belly (p, rest))))
let newSnake = snakeZip unzipped (eaten+1)
let nextFood = Graphics.getFood grid
GameRunning (Up shiftUp, newSnake, grid, eaten+1, nextFood)
| pivot ->
let unzipped = snakeUnzip (Head (pivot, (Belly (p, rest))))
let newSnake = snakeZip unzipped eaten
GameRunning (Up pivot, newSnake, grid, eaten, food)
with _ -> GameOver (grid, eaten)
| _ -> failwith "ERROR: No head!"
let moveDown (snake:Snake<Position>) (grid:Grid) (eaten:int) (food:Food) : Game =
match snake with
| Head (p, rest:Snake<Position>) ->
let x, y = p
let shiftDown = ((x+1), y)
try
match shiftDown with
| (row,column) when Graphics.isOccupied grid.[row].[column] ->
GameOver (grid, eaten)
| (row, column) when Graphics.isFood grid.[row].[column] ->
let unzipped = snakeUnzip (Head (shiftDown, (Belly (p, rest))))
let newSnake = snakeZip unzipped (eaten+1)
let nextFood = Graphics.getFood grid
GameRunning (Down shiftDown, newSnake, grid, (eaten+1), nextFood)
| pivot ->
let unzipped = snakeUnzip (Head (pivot, (Belly (p, rest))))
let newSnake = snakeZip unzipped eaten
GameRunning (Down pivot, newSnake, grid, eaten, food)
with _ -> GameOver (grid, eaten)
| _ -> failwith "ERROR: No head!"
let moveLeft (snake:Snake<Position>) (grid:Grid) (eaten:int) (food:Food) : Game =
match snake with
| Head (p, rest:Snake<Position>) ->
let x, y = p
let shiftLeft = (x, (y-1))
try
match shiftLeft with
| (row,column) when Graphics.isOccupied grid.[row].[column] ->
GameOver (grid, eaten)
| (row, column) when Graphics.isFood grid.[row].[column] ->
let unzipped = snakeUnzip (Head (shiftLeft, (Belly (p, rest))))
let newSnake = snakeZip unzipped (eaten+1)
let nextFood = Graphics.getFood grid
GameRunning (Left shiftLeft, newSnake, grid, eaten+1, nextFood)
| pivot ->
let unzipped = snakeUnzip (Head (pivot, (Belly (p, rest))))
let newSnake = snakeZip unzipped eaten
GameRunning (Left pivot, newSnake, grid, eaten, food)
with _ -> GameOver (grid, eaten)
| _ -> failwith "ERROR: No head!"
let moveRight (snake:Snake<Position>) (grid:Grid) (eaten:int) (food:Food) : Game =
match snake with
| Head (p, rest:Snake<Position>) ->
let (x: int), y = p
let shiftRight = (x, (y+1))
try
match shiftRight with
| (row,column) when Graphics.isOccupied grid.[row].[column] ->
GameOver (grid, eaten)
| (row, column) when Graphics.isFood grid.[row].[column] ->
let unzipped = snakeUnzip (Head (shiftRight, (Belly (p, rest))))
let newSnake = snakeZip unzipped (eaten+1)
let nextFood = Graphics.getFood grid
GameRunning (Right shiftRight, newSnake, grid, eaten+1, nextFood)
| pivot ->
let unzipped = snakeUnzip (Head (pivot, (Belly (p, rest))))
let newSnake = snakeZip unzipped eaten
GameRunning (Right pivot, newSnake, grid, eaten, food)
with _ -> GameOver (grid, eaten)
| _ -> failwith "ERROR: No head!"
open SnakeGame
[<EntryPoint>]
let main _ =
/// A gentle slope function for making the snake go faster:
let tick (eaten:int) = 100./log10(float eaten) |> int
let getNextMove prev snake grid eaten food : Task<Game> =
task {
do! Task.Delay(tick(eaten))
if not Console.KeyAvailable
then
match prev with
| Up _ -> return GamePlay.moveUp snake grid eaten food
| Down _ -> return GamePlay.moveDown snake grid eaten food
| Right _ -> return GamePlay.moveRight snake grid eaten food
| Left _ -> return GamePlay.moveLeft snake grid eaten food
| InvalidMove -> return GameOver (grid, eaten)
else
match Console.ReadKey() with
| keystroke when keystroke.Key.Equals(ConsoleKey.UpArrow) ->
return GamePlay.moveUp snake grid eaten food
| keystroke when keystroke.Key.Equals(ConsoleKey.DownArrow) ->
return GamePlay.moveDown snake grid eaten food
| keystroke when keystroke.Key.Equals(ConsoleKey.RightArrow) ->
return GamePlay.moveRight snake grid eaten food
| keystroke when keystroke.Key.Equals(ConsoleKey.LeftArrow) ->
return GamePlay.moveLeft snake grid eaten food
| _ ->
match prev with
| Up _ -> return GamePlay.moveUp snake grid eaten food
| Down _ -> return GamePlay.moveDown snake grid eaten food
| Right _ -> return GamePlay.moveRight snake grid eaten food
| Left _ -> return GamePlay.moveLeft snake grid eaten food
| InvalidMove -> return GameOver (grid, eaten)
}
let gridDimension = 20
let segments = [(0,3); (0,2); (0,1); (0,0)]
let youngSnake : Snake<Position> = snakeZip segments segments.Length
let startingGrid = Graphics.makeGrid gridDimension
let startingFood = Graphics.getFood startingGrid
let start = GamePlay.moveRight youngSnake startingGrid segments.Length startingFood
let rec gameLoop (game:Game) =
match game with
| GameRunning (prev, snake, grid, eaten, food) ->
do Graphics.clearGrid grid
do Graphics.dropFood grid food
do Graphics.slither snake grid
do Graphics.render grid
let eitherPlayerOrCursor = getNextMove prev snake grid eaten food
do eitherPlayerOrCursor.Wait()
gameLoop eitherPlayerOrCursor.Result
| GameOver (grid,eaten) ->
do Graphics.endGame grid
printfn $"Game Over! Snake ate {eaten-segments.Length} critters!"
do Thread.Sleep(1000)
let rec wantToPlayAgain () =
match Console.ReadKey() with
| keystroke when keystroke.Key.Equals(ConsoleKey.Y) -> gameLoop start
| keystroke when keystroke.Key.Equals(ConsoleKey.N) -> ()
| _ -> wantToPlayAgain ()
printfn $"Restart? Type Y to continue..."
wantToPlayAgain()
do gameLoop start
0
Go
This uses the termbox package for terminal input and output. This makes the code fairly cross-platform, it successfully built for FreeBSD, OpenBSD, NetBSD, DragonFly BSD, Linux, MS Windows, and MacOS (tested on FreeBSD and MS Windows).
package main
import (
"errors"
"fmt"
"log"
"math/rand"
"time"
termbox "github.com/nsf/termbox-go"
)
func main() {
rand.Seed(time.Now().UnixNano())
score, err := playSnake()
if err != nil {
log.Fatal(err)
}
fmt.Println("Final score:", score)
}
type snake struct {
body []position // tail to head positions of the snake
heading direction
width, height int
cells []termbox.Cell
}
type position struct {
X int
Y int
}
type direction int
const (
North direction = iota
East
South
West
)
func (p position) next(d direction) position {
switch d {
case North:
p.Y--
case East:
p.X++
case South:
p.Y++
case West:
p.X--
}
return p
}
func playSnake() (int, error) {
err := termbox.Init()
if err != nil {
return 0, err
}
defer termbox.Close()
termbox.Clear(fg, bg)
termbox.HideCursor()
s := &snake{
// It would be more efficient to use a circular
// buffer instead of a plain slice for s.body.
body: make([]position, 0, 32),
cells: termbox.CellBuffer(),
}
s.width, s.height = termbox.Size()
s.drawBorder()
s.startSnake()
s.placeFood()
s.flush()
moveCh, errCh := s.startEventLoop()
const delay = 125 * time.Millisecond
for t := time.NewTimer(delay); ; t.Reset(delay) {
var move direction
select {
case err = <-errCh:
return len(s.body), err
case move = <-moveCh:
if !t.Stop() {
<-t.C // handles race between moveCh and t.C
}
case <-t.C:
move = s.heading
}
if s.doMove(move) {
time.Sleep(1 * time.Second)
break
}
}
return len(s.body), err
}
func (s *snake) startEventLoop() (<-chan direction, <-chan error) {
moveCh := make(chan direction)
errCh := make(chan error, 1)
go func() {
defer close(errCh)
for {
switch ev := termbox.PollEvent(); ev.Type {
case termbox.EventKey:
switch ev.Ch { // WSAD and HJKL movement
case 'w', 'W', 'k', 'K':
moveCh <- North
case 'a', 'A', 'h', 'H':
moveCh <- West
case 's', 'S', 'j', 'J':
moveCh <- South
case 'd', 'D', 'l', 'L':
moveCh <- East
case 0:
switch ev.Key { // Cursor key movement
case termbox.KeyArrowUp:
moveCh <- North
case termbox.KeyArrowDown:
moveCh <- South
case termbox.KeyArrowLeft:
moveCh <- West
case termbox.KeyArrowRight:
moveCh <- East
case termbox.KeyEsc: // Quit
return
}
}
case termbox.EventResize:
// TODO
errCh <- errors.New("terminal resizing unsupported")
return
case termbox.EventError:
errCh <- ev.Err
return
case termbox.EventInterrupt:
return
}
}
}()
return moveCh, errCh
}
func (s *snake) flush() {
termbox.Flush()
s.cells = termbox.CellBuffer()
}
func (s *snake) getCellRune(p position) rune {
i := p.Y*s.width + p.X
return s.cells[i].Ch
}
func (s *snake) setCell(p position, c termbox.Cell) {
i := p.Y*s.width + p.X
s.cells[i] = c
}
func (s *snake) drawBorder() {
for x := 0; x < s.width; x++ {
s.setCell(position{x, 0}, border)
s.setCell(position{x, s.height - 1}, border)
}
for y := 0; y < s.height-1; y++ {
s.setCell(position{0, y}, border)
s.setCell(position{s.width - 1, y}, border)
}
}
func (s *snake) placeFood() {
for {
// a random empty cell
x := rand.Intn(s.width-2) + 1
y := rand.Intn(s.height-2) + 1
foodp := position{x, y}
r := s.getCellRune(foodp)
if r != ' ' {
continue
}
s.setCell(foodp, food)
return
}
}
func (s *snake) startSnake() {
// a random cell somewhat near the center
x := rand.Intn(s.width/2) + s.width/4
y := rand.Intn(s.height/2) + s.height/4
head := position{x, y}
s.setCell(head, snakeHead)
s.body = append(s.body[:0], head)
s.heading = direction(rand.Intn(4))
}
func (s *snake) doMove(move direction) bool {
head := s.body[len(s.body)-1]
s.setCell(head, snakeBody)
head = head.next(move)
s.heading = move
s.body = append(s.body, head)
r := s.getCellRune(head)
s.setCell(head, snakeHead)
gameOver := false
switch r {
case food.Ch:
s.placeFood()
case border.Ch, snakeBody.Ch:
gameOver = true
fallthrough
case empty.Ch:
s.setCell(s.body[0], empty)
s.body = s.body[1:]
default:
panic(r)
}
s.flush()
return gameOver
}
const (
fg = termbox.ColorWhite
bg = termbox.ColorBlack
)
// Symbols to use.
// Could use Unicode instead of simple ASCII.
var (
empty = termbox.Cell{Ch: ' ', Bg: bg, Fg: fg}
border = termbox.Cell{Ch: '+', Bg: bg, Fg: termbox.ColorBlue}
snakeBody = termbox.Cell{Ch: '#', Bg: bg, Fg: termbox.ColorGreen}
snakeHead = termbox.Cell{Ch: 'O', Bg: bg, Fg: termbox.ColorYellow | termbox.AttrBold}
food = termbox.Cell{Ch: '@', Bg: bg, Fg: termbox.ColorRed}
)
Haskell
{-# LANGUAGE TemplateHaskell #-}
import Control.Monad.Random (getRandomRs)
import Graphics.Gloss.Interface.Pure.Game
import Lens.Micro ((%~), (^.), (&), set)
import Lens.Micro.TH (makeLenses)
--------------------------------------------------------------------------------
-- all data types
data Snake = Snake { _body :: [Point], _direction :: Point }
makeLenses ''Snake
data World = World { _snake :: Snake , _food :: [Point]
, _score :: Int , _maxScore :: Int }
makeLenses ''World
--------------------------------------------------------------------------------
-- everything snake can do
moves (Snake b d) = Snake (step b d : init b) d
eats (Snake b d) = Snake (step b d : b) d
bites (Snake b _) = any (== head b)
step ((x,y):_) (a,b) = (x+a, y+b)
turn (x',y') (Snake b (x,y)) | (x+x',y+y') == (0,0) = Snake b (x,y)
| otherwise = Snake b (x',y')
--------------------------------------------------------------------------------
-- all randomness
createWorld = do xs <- map fromIntegral <$> getRandomRs (2, 38 :: Int)
ys <- map fromIntegral <$> getRandomRs (2, 38 :: Int)
return (Ok, World snake (zip xs ys) 0 0)
where
snake = Snake [(20, 20)] (1,0)
-------------------------------------------------------------------------------
-- A tyny DSL for declarative description of business logic
data Status = Ok | Fail | Stop
continue = \x -> (Ok, x)
stop = \x -> (Stop, x)
f >>> g = \x -> case f x of { (Ok, y) -> g y; b -> b } -- chain composition
f <|> g = \x -> case f x of { (Fail, _) -> g x; b -> b } -- alternative
p ==> f = \x -> if p x then f x else (Fail, x) -- condition
l .& f = continue . (l %~ f) -- modification
l .= y = continue . set l y -- setting
--------------------------------------------------------------------------------
-- all business logic
updateWorld _ = id >>> (snakeEats <|> snakeMoves)
where
snakeEats = (snakeFindsFood ==> (snake .& eats)) >>>
(score .& (+1)) >>> (food .& tail)
snakeMoves = (snakeBitesTail ==> stop) <|>
(snakeHitsWall ==> stop) <|>
(snake .& moves)
snakeFindsFood w = (w^.snake & moves) `bites` (w^.food & take 1)
snakeBitesTail w = (w^.snake) `bites` (w^.snake.body & tail)
snakeHitsWall w = (w^.snake.body) & head & isOutside
isOutside (x,y) = or [x <= 0, 40 <= x, y <= 0, 40 <= y]
--------------------------------------------------------------------------------
-- all event handing
handleEvents e (s,w) = f w
where f = case s of
Ok -> case e of
EventKey (SpecialKey k) _ _ _ -> case k of
KeyRight -> snake .& turn (1,0)
KeyLeft -> snake .& turn (-1,0)
KeyUp -> snake .& turn (0,1)
KeyDown -> snake .& turn (0,-1)
_-> continue
_-> continue
_-> \w -> w & ((snake.body) .= [(20,20)]) >>>
(maxScore .& max (w^.score)) >>> (score .= 0)
--------------------------------------------------------------------------------
-- all graphics
renderWorld (s, w) = pictures [frame, color c drawSnake, drawFood, showScore]
where c = case s of { Ok -> orange; _-> red }
drawSnake = foldMap (rectangleSolid 10 10 `at`) (w^.snake.body)
drawFood = color blue $ circleSolid 5 `at` (w^.food & head)
frame = color black $ rectangleWire 400 400
showScore = color orange $ scale 0.2 0.2 $ txt `at` (-80,130)
txt = Text $ mconcat ["Score: ", w^.score & show
," Maximal score: ", w^.maxScore & show]
at p (x,y) = Translate (10*x-200) (10*y-200) p
--------------------------------------------------------------------------------
main = do world <- createWorld
play inW white 7 world renderWorld handleEvents updateWorld
where inW = InWindow "The Snake" (400, 400) (10, 10)
Extra credit
It is easy to make snake to seek food automatically. Just change the first line of the updateWorld
definition:
updateWorld _ = id >>> snakeSeeksFood >>> (snakeEats <|> snakeMoves)
and add local definition:
snakeSeeksFood w = w & snake .& turns optimalDirection
where
optimalDirection = minimumBy (comparing distanceToFood) safeTurns
safeTurns = filter safe [(x,y),(-y,x),(y,-x)] `ifEmpty` [(x,y)]
where (x,y) = w^.snake.direction
safe d = let w'' = w & snake %~ moves . turns d
in not (snakeBitesTail w'' || snakeHitsWall w'')
lst `ifEmpty` x = if null lst then x else lst
distanceToFood d = let (a,b) = w^.snake & turns d & moves & (^.body) & head
(x,y) = w^.food & head
in (a-x)^2 + (b-y)^2
J
Needs j9 qt:
Use WASD to move. Use start
to start.
Difficulty may be adjusted by providing a non-default argument to start (time in milliseconds between animation updates). For example, start 200
for an easier game.
In this implementation, speed is fixed (and final score is by the update rate). Another approach might be to start slow and gradually speed based on the length of the snake. Those sorts of changes are left as an exercise for the reader.
require'ide/qt/gl2'
coinsert 'jgl2'
open=: wd@{{)n
pc s closeok;
cc n isidraw;
set n wh 400 400;
pshow;
}}
start=: {{
speed=: {.y,200
open''
snake=: 10 10,10 11,:10 12
newdot''
draw''
}}
newdot=: {{
dot=: ({~ ?@#) snake -.~ ,/(#: i.)40 40
}}
s_n_char=: {{
select. {.toupper sysdata
case. 'W' do. move 0 _1
case. 'A' do. move _1 0
case. 'S' do. move 0 1
case. 'D' do. move 1 0
end.
}}
move=: {{
s_timer=: move@y
wd 'ptimer ',":speed
head=. y+{.snake
if. head e. snake do. head gameover'' return. end.
if. _1 e. head do. (0>.head) gameover'' return. end.
if. 1 e. 40 <: head do. (39<.head) gameover'' return. end.
if. dot -: head do.
snake=: dot,snake
newdot''
else.
snake=: head,}: snake
end.
draw''
}}
draw=: {{
glclear''
glbrush glrgb 0 0 255
glrect 10*}.snake,"1(1 1)
glbrush glrgb 0 255 0
glrect 10*({.snake),1 1
glbrush glrgb 255 0 0
glrect 10*dot,1 1
glpaint''
EMPTY
}}
gameover=: {{
wd 'ptimer 0'
if. 1<#snake do.
echo 'game over'
echo 'score: ',":(#snake)*1000%speed
draw''
glbrush glrgb 255 255 0
glrect 10*x,1 1
end.
snake=: ,:_ _
}}
Java
See Snake/Java.
JavaScript
You need the P5 Library to run this code!
const L = 1, R = 2, D = 4, U = 8;
var block = 24, wid = 30, hei = 20, frameR = 7, fruit, snake;
function Snake() {
this.length = 1;
this.alive = true;
this.pos = createVector( 1, 1 );
this.posArray = [];
this.posArray.push( createVector( 1, 1 ) );
this.dir = R;
this.draw = function() {
fill( 130, 190, 0 );
var pos, i = this.posArray.length - 1, l = this.length;
while( true ){
pos = this.posArray[i--];
rect( pos.x * block, pos.y * block, block, block );
if( --l == 0 ) break;
}
}
this.eat = function( frut ) {
var b = this.pos.x == frut.x && this.pos.y == frut.y;
if( b ) this.length++;
return b;
}
this.overlap = function() {
var len = this.posArray.length - 1;
for( var i = len; i > len - this.length; i-- ) {
tp = this.posArray[i];
if( tp.x === this.pos.x && tp.y === this.pos.y ) return true;
}
return false;
}
this.update = function() {
if( !this.alive ) return;
switch( this.dir ) {
case L:
this.pos.x--; if( this.pos.x < 1 ) this.pos.x = wid - 2;
break;
case R:
this.pos.x++; if( this.pos.x > wid - 2 ) this.pos.x = 1;
break;
case U:
this.pos.y--; if( this.pos.y < 1 ) this.pos.y = hei - 2;
break;
case D:
this.pos.y++; if( this.pos.y > hei - 2 ) this.pos.y = 1;
break;
}
if( this.overlap() ) { this.alive = false; } else {
this.posArray.push( createVector( this.pos.x, this.pos.y ) );
if( this.posArray.length > 5000 ) { this.posArray.splice( 0, 1 ); }
}
}
}
function Fruit() {
this.fruitTime = true;
this.pos = createVector();
this.draw = function() {
fill( 200, 50, 20 );
rect( this.pos.x * block, this.pos.y * block, block, block );
}
this.setFruit = function() {
this.pos.x = floor( random( 1, wid - 1 ) );
this.pos.y = floor( random( 1, hei - 1 ) );
this.fruitTime = false;
}
}
function setup() {
createCanvas( block * wid, block * hei );
noStroke(); frameRate( frameR );
snake = new Snake();fruit = new Fruit();
}
function keyPressed() {
switch( keyCode ) {
case LEFT_ARROW: snake.dir = L; break;
case RIGHT_ARROW: snake.dir = R; break;
case UP_ARROW: snake.dir = U; break;
case DOWN_ARROW: snake.dir = D;
}
}
function draw() {
background( color( 0, 0x22, 0 ) );
fill( 20, 50, 120 );
for( var i = 0; i < wid; i++ ) {
rect( i * block, 0, block, block );
rect( i * block, height - block, block, block );
}
for( var i = 1; i < hei - 1; i++ ) {
rect( 1, i * block, block, block );
rect( width - block, i * block, block, block );
}
if( fruit.fruitTime ) {
fruit.setFruit();
frameR += .2;
frameRate( frameR );
}
fruit.draw();
snake.update();
if( snake.eat( fruit.pos ) ) {
fruit.fruitTime = true;
}
snake.draw();
fill( 200 );
textStyle( BOLD ); textAlign( RIGHT ); textSize( 120 );
text( ""+( snake.length - 1 ), 690, 440 );
if( !snake.alive ) text( "THE END", 630, 250 );
}
Julia
using GLMakie, GeometryBasics
mutable struct SnakeGame
height::Int
width::Int
snake::Vector{CartesianIndex{2}}
food::CartesianIndex{2}
end
function SnakeGame(;height=6, width=8)
snake = [rand(CartesianIndices((height, width)))]
food = rand(CartesianIndices((height, width)))
while food == snake[1]
food = rand(CartesianIndices((height, width)))
end
SnakeGame(height, width, snake, food)
end
function step!(game, direction)
next_head = game.snake[1] + direction
next_head = CartesianIndex(mod.(next_head.I, Base.OneTo.((game.height, game.width)))) # allow crossing boundry
if is_valid(game, next_head)
pushfirst!(game.snake, next_head)
if next_head == game.food
length(game.snake) < game.height * game.width && init_food!(game)
else
pop!(game.snake)
end
true
else
false
end
end
is_valid(game, position) = position ∉ game.snake
function init_food!(game)
p = rand(CartesianIndices((game.height, game.width)))
while !is_valid(game, p)
p = rand(CartesianIndices((game.height, game.width)))
end
game.food = p
end
function play(;n=10,t=0.5)
game = Observable(SnakeGame(;width=n,height=n))
scene = Scene(resolution = (1000, 1000), raw = true, camera = campixel!)
display(scene)
area = scene.px_area
poly!(scene, area)
grid_size = @lift((widths($area)[1] / $game.height, widths($area)[2] / $game.width))
snake_boxes = @lift([FRect2D((p.I .- (1,1)) .* $grid_size , $grid_size) for p in $game.snake])
poly!(scene, snake_boxes, color=:blue, strokewidth = 5, strokecolor = :black)
snake_head_box = @lift(FRect2D(($game.snake[1].I .- (1,1)) .* $grid_size , $grid_size))
poly!(scene, snake_head_box, color=:black)
snake_head = @lift((($game.snake[1].I .- 0.5) .* $grid_size))
scatter!(scene, snake_head, marker='◉', color=:blue, markersize=@lift(minimum($grid_size)))
food_position = @lift(($game.food.I .- (0.5,0.5)) .* $grid_size)
scatter!(scene, food_position, color=:red, marker='♥', markersize=@lift(minimum($grid_size)))
score_text = @lift("Score: $(length($game.snake)-1)")
text!(scene, score_text, color=:gray, position = @lift((widths($area)[1]/2, widths($area)[2])), fontsize = 50, align = (:center, :top))
direction = Ref{Any}(nothing)
on(events(scene).keyboardbutton) do but
if but.action == Keyboard.press || but.action == Keyboard.repeat
if but.key == Keyboard.left
direction[] = CartesianIndex(-1,0)
elseif but.key == Keyboard.up
direction[] = CartesianIndex(0,1)
elseif but.key == Keyboard.down
direction[] = CartesianIndex(0,-1)
elseif but.key == Keyboard.right
direction[] = CartesianIndex(1,0)
end
end
end
last_dir = nothing
while true
# avoid turn back
if !isnothing(direction[]) && (isnothing(last_dir) || direction[] != -last_dir)
last_dir = direction[]
end
if !isnothing(last_dir)
if step!(game[], last_dir)
game[] = game[]
else
break
end
end
sleep(t)
end
end
play()
Kotlin
// Kotlin Native v0.5
import kotlinx.cinterop.*
import platform.posix.*
import platform.windows.*
const val WID = 60
const val HEI = 30
const val MAX_LEN = 600
const val NUL = '\u0000'
enum class Dir { NORTH, EAST, SOUTH, WEST }
class Snake {
val console: HANDLE
var alive = false
val brd = CharArray(WID * HEI)
var dir = Dir.NORTH
val snk = nativeHeap.allocArray<COORD>(MAX_LEN)
lateinit var head: COORD
var tailIdx = 0
var headIdx = 0
var points = 0
init {
console = GetStdHandle(STD_OUTPUT_HANDLE)!!
SetConsoleTitleW("Snake")
memScoped {
val coord = alloc<COORD>().apply { X = (WID + 1).toShort(); Y = (HEI + 2).toShort() }
SetConsoleScreenBufferSize(console, coord.readValue())
val rc = alloc<SMALL_RECT>().apply {
Left = 0; Top = 0; Right = WID.toShort(); Bottom = (HEI + 1).toShort()
}
SetConsoleWindowInfo(console, TRUE, rc.ptr)
val ci = alloc<CONSOLE_CURSOR_INFO>().apply { dwSize = 1; bVisible = FALSE }
SetConsoleCursorInfo(console, ci.ptr)
}
}
fun play() {
while (true) {
createfield()
alive = true
while (alive) {
drawfield()
readKey()
moveSnake()
Sleep(50)
}
memScoped {
val c = alloc<COORD>().apply { X = 0; Y = (HEI + 1).toShort() }
SetConsoleCursorPosition(console, c.readValue())
}
SetConsoleTextAttribute(console, 0x000b)
print("Play again [Y/N]? ")
val a = readLine()!!.toLowerCase()
if (a.length > 0 && a[0] != 'y') {
nativeHeap.free(snk)
return
}
}
}
private fun createfield() {
memScoped {
val coord = alloc<COORD>().apply { X = 0; Y = 0 }
val c = alloc<DWORDVar>()
FillConsoleOutputCharacterW(console, 32, (HEI + 2) * 80, coord.readValue(), c.ptr)
FillConsoleOutputAttribute(console, 0x0000, (HEI + 2) * 80, coord.readValue(), c.ptr)
SetConsoleCursorPosition(console, coord.readValue())
}
for (x in 0 until WID * HEI) brd[x] = NUL
for (x in 0 until WID) {
brd[x + WID * (HEI - 1)] = '+'
brd[x] = '+'
}
for (y in 1 until HEI) {
brd[WID - 1 + WID * y] = '+'
brd[WID * y] = '+'
}
var xx: Int
var yy: Int
do {
xx = rand() % WID
yy = rand() % (HEI shr 1) + (HEI shr 1)
}
while (brd[xx + WID * yy] != NUL)
brd[xx + WID * yy] = '@'
tailIdx = 0
headIdx = 4
xx = 3
yy = 2
for (cc in tailIdx until headIdx) {
brd[xx + WID * yy] = '#'
snk[cc].X = (3 + cc).toShort()
snk[cc].Y = 2
}
head = snk[3]
dir = Dir.EAST
points = 0
}
private fun readKey() {
if ((GetAsyncKeyState(39).toInt() and 0x8000) != 0) dir = Dir.EAST
if ((GetAsyncKeyState(37).toInt() and 0x8000) != 0) dir = Dir.WEST
if ((GetAsyncKeyState(38).toInt() and 0x8000) != 0) dir = Dir.NORTH
if ((GetAsyncKeyState(40).toInt() and 0x8000) != 0) dir = Dir.SOUTH
}
private fun drawfield() {
memScoped {
val coord = alloc<COORD>()
var t = NUL
for (y in 0 until HEI) {
coord.Y = y.toShort()
for (x in 0 until WID) {
t = brd[x + WID * y]
if (t == NUL) continue
coord.X = x.toShort()
SetConsoleCursorPosition(console, coord.readValue())
if (coord.X == head.X && coord.Y == head.Y) {
SetConsoleTextAttribute(console, 0x002e)
print('O')
SetConsoleTextAttribute(console, 0x0000)
continue
}
when (t) {
'#' -> SetConsoleTextAttribute(console, 0x002a)
'+' -> SetConsoleTextAttribute(console, 0x0019)
'@' -> SetConsoleTextAttribute(console, 0x004c)
}
print(t)
SetConsoleTextAttribute(console, 0x0000)
}
}
print(t)
SetConsoleTextAttribute(console, 0x0007)
val c = alloc<COORD>().apply { X = 0; Y = HEI.toShort() }
SetConsoleCursorPosition(console, c.readValue())
print("Points: $points")
}
}
private fun moveSnake() {
when (dir) {
Dir.NORTH -> head.Y--
Dir.EAST -> head.X++
Dir.SOUTH -> head.Y++
Dir.WEST -> head.X--
}
val t = brd[head.X + WID * head.Y]
if (t != NUL && t != '@') {
alive = false
return
}
brd[head.X + WID * head.Y] = '#'
snk[headIdx].X = head.X
snk[headIdx].Y = head.Y
if (++headIdx >= MAX_LEN) headIdx = 0
if (t == '@') {
points++
var x: Int
var y: Int
do {
x = rand() % WID
y = rand() % (HEI shr 1) + (HEI shr 1)
}
while (brd[x + WID * y] != NUL)
brd[x + WID * y] = '@'
return
}
SetConsoleCursorPosition(console, snk[tailIdx].readValue())
print(' ')
brd[snk[tailIdx].X + WID * snk[tailIdx].Y] = NUL
if (++tailIdx >= MAX_LEN) tailIdx = 0
}
}
fun main(args: Array<String>) {
srand(time(null).toInt())
Snake().play()
}
- Output:
Similar to C++ entry
Lua
UP, RIGHT, DOWN, LEFT = 1, 2, 3, 4
UpdateTime=0.200
Timer = 0
GridSize = 30
GridWidth, GridHeight = 20, 10
local directions = {
[UP] = {x= 0, y=-1},
[RIGHT] = {x= 1, y= 0},
[DOWN] = {x= 0, y= 1},
[LEFT] = {x=-1, y= 0},
}
local function isPositionInBody(x, y)
for i = 1, #Body-3, 2 do -- skip tail, it moves before we get in
if x == Body[i] and y == Body[i+1] then
return true
end
end
return false
end
local function isPositionInApple(x, y)
if x == Apple.x and y == Apple.y then
return true
end
return false
end
local function newApple ()
local ApplePlaced = false
while not ApplePlaced do
local x = GridSize*math.random (GridWidth)
local y = GridSize*math.random (GridHeight)
if not isPositionInBody(x, y) then
Apple = {x=x, y=y}
ApplePlaced = true
end
end
end
local function newGame ()
Score = 0
GameOver = false
local x = GridSize*math.floor(math.random (0.25*GridWidth, 0.75*GridWidth))
print (x)
local y = GridSize*math.floor(math.random (0.25*GridHeight, 0.75*GridHeight))
print (y)
local iDirection = math.random(4)
local d = directions[iDirection]
Head = {
x=x,
y=y,
iDirection = iDirection,
nextDirection = iDirection,
}
Body = {x, y, x-GridSize*d.x, y-GridSize*d.y}
Apples = {}
newApple ()
end
function love.load()
newGame ()
end
local function moveSnake (x, y, iDirection, longer)
table.insert (Body, 1, x)
table.insert (Body, 2, y)
Head.x = x
Head.y = y
Head.iDirection = iDirection
if not longer then
-- remove last pair
table.remove(Body)
table.remove(Body)
end
if x <= 0 or x > GridSize*(GridWidth) or
y <= 0 or y > GridSize*(GridHeight) then
GameOver = true
end
end
function love.update(dt)
Timer = Timer + dt
if Timer < UpdateTime then return end
Timer = Timer - UpdateTime
local iDirection = Head.nextDirection
local d = directions[iDirection]
local x, y = Head.x+GridSize*d.x, Head.y+GridSize*d.y
if isPositionInBody(x, y) then
GameOver = true
elseif isPositionInApple(x, y) then
Score = Score + 1
newApple ()
moveSnake (x, y, iDirection, true)
else
moveSnake (x, y, iDirection, false)
end
end
function drawHead () -- position, length, width and angle
love.graphics.push()
love.graphics.translate(Head.x, Head.y)
love.graphics.rotate((Head.iDirection-2)*math.pi/2)
love.graphics.polygon("fill",
-GridSize/3, -GridSize /3,
-GridSize/3, GridSize /3,
GridSize/3, 0)
love.graphics.pop()
end
function love.draw()
love.graphics.setColor(0,1,0)
love.graphics.print ('Score: '..tostring(Score), 10, 10)
if GameOver then
love.graphics.print ('Game Over: '..tostring(GameOver)..'. Press "Space" to continue', 10, 30)
else
love.graphics.translate(GridSize, GridSize)
love.graphics.setColor(0.6,0.6,0.6)
love.graphics.setLineWidth(0.25)
for x = GridSize, GridSize*GridWidth, GridSize do
love.graphics.line (x, GridSize, x, GridSize*GridHeight)
end
for y = GridSize, GridSize*GridHeight, GridSize do
love.graphics.line (GridSize, y, GridSize*GridWidth, y)
end
love.graphics.setLineWidth((GridSize/4)+0.5)
love.graphics.setColor(1,1,1)
love.graphics.line (Body)
drawHead ()
love.graphics.setColor(1,0,0)
love.graphics.circle ('fill', Apple.x, Apple.y, GridSize/4)
end
end
function love.keypressed(key, scancode, isrepeat)
if false then
elseif key == "space" then
if GameOver then
GameOver = false
newGame ()
end
elseif key == "escape" then
love.event.quit()
else
local iDirection = Head.iDirection
if iDirection == UP or
iDirection == DOWN then
local right = love.keyboard.isScancodeDown ("d")
local left = love.keyboard.isScancodeDown ("a")
if right and not left then
iDirection = RIGHT
elseif left and not right then
iDirection = LEFT
end
else -- right or left
local down = love.keyboard.isScancodeDown ("s")
local up = love.keyboard.isScancodeDown ("w")
if up and not down then
iDirection = UP
elseif down and not up then
iDirection = DOWN
end
end
Head.nextDirection = iDirection
end
end
Nim
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
(* 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
![](http://static.miraheze.org/rosettacodewiki/thumb/0/02/Snake_game_perl.png/200px-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
constant W = 60, H = 30, MAX_LEN = 600
enum NORTH, EAST, SOUTH, WEST
sequence board, snake
bool alive
integer tailIdx, headIdx, hdX, hdY, d, points
procedure createField()
clear_screen()
board = repeat("+"&repeat(' ',W-2)&'+',H)
for x=1 to W do
board[1,x] = '+'
end for
board[H] = board[1]
board[1+rand(H-2),1+rand(W-2)] = '@';
snake = repeat(0,MAX_LEN)
board[3,4] = '#'; tailIdx = 1; headIdx = 5;
for c=tailIdx to headIdx do
snake[c] = {3,3+c}
end for
{hdY,hdX} = snake[headIdx-1]; d = EAST; points = 0;
end procedure
procedure drawField()
for y=1 to H do
for x=1 to W do
integer t = board[y,x]
if t!=' ' then
position(y,x)
if x=hdX and y=hdY then
text_color(14); puts(1,'O');
else
text_color({10,9,12}[find(t,"#+@")]); puts(1,t);
end if
end if
end for
end for
position(H+1,1); text_color(7); printf(1,"Points: %d",points)
end procedure
procedure readKey()
integer k = find(get_key(),{333,331,328,336})
if k then d = {EAST,WEST,NORTH,SOUTH}[k] end if
end procedure
procedure moveSnake()
integer x,y
switch d do
case NORTH: hdY -= 1
case EAST: hdX += 1
case SOUTH: hdY += 1
case WEST: hdX -= 1
end switch
integer t = board[hdY,hdX];
if t!=' ' and t!='@' then alive = false; return; end if
board[hdY,hdX] = '#'; snake[headIdx] = {hdY,hdX};
headIdx += 1; if headIdx>MAX_LEN then headIdx = 1 end if
if t=='@' then
points += 1
while 1 do
x = 1+rand(W-2); y = 1+rand(H-2);
if board[y,x]=' ' then
board[y,x] = '@'
return
end if
end while
end if
{y,x} = snake[tailIdx]; position(y,x); puts(1,' '); board[y,x] = ' ';
tailIdx += 1; if tailIdx>MAX_LEN then tailIdx = 1 end if
end procedure
procedure play()
while true do
createField(); alive = true; cursor(NO_CURSOR)
while alive do drawField(); readKey(); moveSnake(); sleep(0.05) end while
cursor(BLOCK_CURSOR); position(H+2,1); bk_color(0); text_color(11);
puts(1,"Play again [Y/N]? ")
if upper(wait_key())!='Y' then return end if
end while
end procedure
play()
Python
Using Pygame. Works with Python >= 3.7.
from __future__ import annotations
import itertools
import random
from enum import Enum
from typing import Any
from typing import Tuple
import pygame as pg
from pygame import Color
from pygame import Rect
from pygame.surface import Surface
from pygame.sprite import AbstractGroup
from pygame.sprite import Group
from pygame.sprite import RenderUpdates
from pygame.sprite import Sprite
class Direction(Enum):
UP = (0, -1)
DOWN = (0, 1)
LEFT = (-1, 0)
RIGHT = (1, 0)
def opposite(self, other: Direction):
return (self[0] + other[0], self[1] + other[1]) == (0, 0)
def __getitem__(self, i: int):
return self.value[i]
class SnakeHead(Sprite):
def __init__(
self,
size: int,
position: Tuple[int, int],
facing: Direction,
bounds: Rect,
) -> None:
super().__init__()
self.image = Surface((size, size))
self.image.fill(Color("aquamarine4"))
self.rect = self.image.get_rect()
self.rect.center = position
self.facing = facing
self.size = size
self.speed = size
self.bounds = bounds
def update(self, *args: Any, **kwargs: Any) -> None:
# Move the snake in the direction it is facing.
self.rect.move_ip(
(
self.facing[0] * self.speed,
self.facing[1] * self.speed,
)
)
# Move to the opposite side of the screen if the snake goes out of bounds.
if self.rect.right > self.bounds.right:
self.rect.left = 0
elif self.rect.left < 0:
self.rect.right = self.bounds.right
if self.rect.bottom > self.bounds.bottom:
self.rect.top = 0
elif self.rect.top < 0:
self.rect.bottom = self.bounds.bottom
def change_direction(self, direction: Direction):
if not self.facing == direction and not direction.opposite(self.facing):
self.facing = direction
class SnakeBody(Sprite):
def __init__(
self,
size: int,
position: Tuple[int, int],
colour: str = "white",
) -> None:
super().__init__()
self.image = Surface((size, size))
self.image.fill(Color(colour))
self.rect = self.image.get_rect()
self.rect.center = position
class Snake(RenderUpdates):
def __init__(self, game: Game) -> None:
self.segment_size = game.segment_size
self.colours = itertools.cycle(["aquamarine1", "aquamarine3"])
self.head = SnakeHead(
size=self.segment_size,
position=game.rect.center,
facing=Direction.RIGHT,
bounds=game.rect,
)
neck = [
SnakeBody(
size=self.segment_size,
position=game.rect.center,
colour=next(self.colours),
)
for _ in range(2)
]
super().__init__(*[self.head, *neck])
self.body = Group()
self.tail = neck[-1]
def update(self, *args: Any, **kwargs: Any) -> None:
self.head.update()
# Snake body sprites don't update themselves. We update them here.
segments = self.sprites()
for i in range(len(segments) - 1, 0, -1):
# Current sprite takes the position of the previous sprite.
segments[i].rect.center = segments[i - 1].rect.center
def change_direction(self, direction: Direction):
self.head.change_direction(direction)
def grow(self):
tail = SnakeBody(
size=self.segment_size,
position=self.tail.rect.center,
colour=next(self.colours),
)
self.tail = tail
self.add(self.tail)
self.body.add(self.tail)
class SnakeFood(Sprite):
def __init__(self, game: Game, size: int, *groups: AbstractGroup) -> None:
super().__init__(*groups)
self.image = Surface((size, size))
self.image.fill(Color("red"))
self.rect = self.image.get_rect()
self.rect.topleft = (
random.randint(0, game.rect.width),
random.randint(0, game.rect.height),
)
self.rect.clamp_ip(game.rect)
# XXX: This approach to random food placement might end badly if the
# snake is very large.
while pg.sprite.spritecollideany(self, game.snake):
self.rect.topleft = (
random.randint(0, game.rect.width),
random.randint(0, game.rect.height),
)
self.rect.clamp_ip(game.rect)
class Game:
def __init__(self) -> None:
self.rect = Rect(0, 0, 640, 480)
self.background = Surface(self.rect.size)
self.background.fill(Color("black"))
self.score = 0
self.framerate = 16
self.segment_size = 10
self.snake = Snake(self)
self.food_group = RenderUpdates(SnakeFood(game=self, size=self.segment_size))
pg.init()
def _init_display(self) -> Surface:
bestdepth = pg.display.mode_ok(self.rect.size, 0, 32)
screen = pg.display.set_mode(self.rect.size, 0, bestdepth)
pg.display.set_caption("Snake")
pg.mouse.set_visible(False)
screen.blit(self.background, (0, 0))
pg.display.flip()
return screen
def draw(self, screen: Surface):
dirty = self.snake.draw(screen)
pg.display.update(dirty)
dirty = self.food_group.draw(screen)
pg.display.update(dirty)
def update(self, screen):
self.food_group.clear(screen, self.background)
self.food_group.update()
self.snake.clear(screen, self.background)
self.snake.update()
def main(self) -> int:
screen = self._init_display()
clock = pg.time.Clock()
while self.snake.head.alive():
for event in pg.event.get():
if event.type == pg.QUIT or (
event.type == pg.KEYDOWN and event.key in (pg.K_ESCAPE, pg.K_q)
):
return self.score
# Change direction using the arrow keys.
keystate = pg.key.get_pressed()
if keystate[pg.K_RIGHT]:
self.snake.change_direction(Direction.RIGHT)
elif keystate[pg.K_LEFT]:
self.snake.change_direction(Direction.LEFT)
elif keystate[pg.K_UP]:
self.snake.change_direction(Direction.UP)
elif keystate[pg.K_DOWN]:
self.snake.change_direction(Direction.DOWN)
# Detect collisions after update.
self.update(screen)
# Snake eats food.
for food in pg.sprite.spritecollide(
self.snake.head, self.food_group, dokill=False
):
food.kill()
self.snake.grow()
self.score += 1
# Increase framerate to speed up gameplay.
if self.score % 5 == 0:
self.framerate += 1
self.food_group.add(SnakeFood(self, self.segment_size))
# Snake hit its own tail.
if pg.sprite.spritecollideany(self.snake.head, self.snake.body):
self.snake.head.kill()
self.draw(screen)
clock.tick(self.framerate)
return self.score
if __name__ == "__main__":
game = Game()
score = game.main()
print(score)
Raku
(formerly Perl 6)
This is a variation of a demo script included in the examples folder for the Raku SDL2::Raw library bindings.
use SDL2::Raw;
use Cairo;
constant W = 1280;
constant H = 960;
constant FIELDW = W div 32;
constant FIELDH = H div 32;
SDL_Init(VIDEO);
my $window = SDL_CreateWindow(
'Snake',
SDL_WINDOWPOS_CENTERED_MASK,
SDL_WINDOWPOS_CENTERED_MASK,
W, H,
OPENGL
);
my $render = SDL_CreateRenderer($window, -1, ACCELERATED +| PRESENTVSYNC);
my $snake_image = Cairo::Image.record(
-> $_ {
.save;
.rectangle: 0, 0, 64, 64;
.clip;
.rgb: 0, 1, 0;
.rectangle: 0, 0, 64, 64;
.fill :preserve;
.rgb: 0, 0, 0;
.stroke;
.restore;
.save;
.translate: 64, 0;
.rectangle: 0, 0, 64, 64;
.clip;
.rgb: 1, 0, 0;
.arc: 32, 32, 30, 0, 2 * pi;
.fill :preserve;
.rgb: 0, 0, 0;
.stroke;
.restore;
}, 128, 128, Cairo::FORMAT_ARGB32);
my $snake_texture = SDL_CreateTexture(
$render,
%PIXELFORMAT<ARGB8888>,
STATIC,
128,
128
);
SDL_UpdateTexture(
$snake_texture,
SDL_Rect.new(
:x(0),
:y(0),
:w(128),
:h(128)
),
$snake_image.data,
$snake_image.stride // 128 * 4
);
SDL_SetTextureBlendMode($snake_texture, 1);
SDL_SetRenderDrawBlendMode($render, 1);
my $snakepiece_srcrect = SDL_Rect.new(:w(64), :h(64));
my $nompiece_srcrect = SDL_Rect.new(:w(64), :h(64), :x(64));
my $event = SDL_Event.new;
enum GAME_KEYS (
K_UP => 82,
K_DOWN => 81,
K_LEFT => 80,
K_RIGHT => 79,
);
my Complex @snakepieces = 10+10i;
my Complex @noms;
my Complex $snakedir = 1+0i;
my $nomspawn = 0;
my $snakespeed = 0.1;
my $snakestep = 0;
my $nom = 4;
my $last_frame_start = now;
main: loop {
my $start = now;
my $dt = $start - $last_frame_start // 0.00001;
while SDL_PollEvent($event) {
my $casted_event = SDL_CastEvent($event);
given $casted_event {
when *.type == QUIT { last main }
when *.type == KEYDOWN {
if GAME_KEYS(.scancode) -> $comm {
given $comm {
when 'K_LEFT' { $snakedir = -1+0i unless $snakedir == 1+0i }
when 'K_RIGHT' { $snakedir = 1+0i unless $snakedir == -1+0i }
when 'K_UP' { $snakedir = 0-1i unless $snakedir == 0+1i }
when 'K_DOWN' { $snakedir = 0+1i unless $snakedir == 0-1i }
}
}
}
}
}
if ($nomspawn -= $dt) < 0 {
$nomspawn += 1;
@noms.push: (^FIELDW).pick + (^FIELDH).pick * i unless @noms > 3;
@noms.pop if @noms[*-1] == any(@snakepieces);
}
if ($snakestep -= $dt) < 0 {
$snakestep += $snakespeed;
@snakepieces.unshift: do given @snakepieces[0] {
($_.re + $snakedir.re) % FIELDW
+ (($_.im + $snakedir.im) % FIELDH) * i
}
if @snakepieces[2..*].first( * == @snakepieces[0], :k ) -> $idx {
@snakepieces = @snakepieces[0..($idx + 1)];
}
@noms .= grep(
{ $^piece == @snakepieces[0] ?? ($nom += 1) && False !! True }
);
if $nom == 0 {
@snakepieces.pop;
} else {
$nom = $nom - 1;
}
}
for @snakepieces {
SDL_SetTextureColorMod(
$snake_texture,
255,
(cos((++$) / 2) * 100 + 155).round,
255
);
SDL_RenderCopy(
$render,
$snake_texture,
$snakepiece_srcrect,
SDL_Rect.new(.re * 32, .im * 32, 32, 32)
);
}
SDL_SetTextureColorMod($snake_texture, 255, 255, 255);
for @noms {
SDL_RenderCopy(
$render,
$snake_texture,
$nompiece_srcrect,
SDL_Rect.new(.re * 32, .im * 32, 32, 32)
)
}
SDL_RenderPresent($render);
SDL_SetRenderDrawColor($render, 0, 0, 0, 0);
SDL_RenderClear($render);
$last_frame_start = $start;
sleep(1 / 50);
}
SDL_Quit();
Rust
Implemented smooth (per-pixel) animation on Win32 API (tested on Windows 7 and Windows 11)
/* add to file Cargo.toml:
[dependencies]
winsafe = "0.0.8" # IMHO: before the appearance of winsafe="0.1" it is not worth raising the version here
rand = "0.8"
derive-new = "0.5"
*/
#![windows_subsystem = "windows"]
use derive_new::new;
use rand::{thread_rng, Rng};
use std::{cell::RefCell, rc::Rc};
use winsafe::{co, gui, prelude::*, COLORREF, HBRUSH, HPEN, RECT, SIZE};
const STEP: i32 = 3; // px, motion per frame
const GCW: i32 = 7; // game grid cell width in STEPs
const SNAKE_W: i32 = 20; // px
const FW: i32 = 20; // the width of the square field in the cells of the game grid
const TW: i32 = (FW + 2) * GCW; // total field width (with overlap for collisions) in STEPs
const ID0: i32 = FW / 2 * GCW; // starting position id
#[rustfmt::skip]
#[derive(new)]
struct Context {
wnd: gui::WindowMain,
#[new(default) ] snake: Vec<i32>, // [rect_ids] where rect_id = y * TW + x (where x, y: nSTEPs)
#[new(value = "[ID0; 6]")] r: [i32; 6], // ID 6 rect to color in next frame (bg, tail, turn, body, food, head)
#[new(default) ] incr: i32, // 0 | -1 | 1 | -TW | TW - increment r[head] in next STEP
#[new(value = "TW") ] next_incr: i32, // `incr` in the next grid cell
#[new(default) ] gap: i32, // interval in STEPs to the next grid cell; negative - tail clipping mark
}
pub fn main() {
let [bg, tail, turn, body, food, head] = [0_usize, 1, 2, 3, 4, 5];
let grid: Vec<_> = (1..=FW).flat_map(|y| (1..=FW).map(move |x| (y * TW + x) * GCW)).collect();
let mut colors = [(0x00, 0xF0, 0xA0); 6]; // color tail, turn, body
colors[bg] = (0x00, 0x50, 0x90);
colors[food] = (0xFF, 0x50, 0x00);
colors[head] = (0xFF, 0xFF, 0x00);
let brushes = COLORREF::new_array(&colors).map(|c| HBRUSH::CreateSolidBrush(c).unwrap());
let wnd = gui::WindowMain::new(gui::WindowMainOpts {
title: "Snake - Start: Space, then press W-A-S-D".to_string(),
size: SIZE::new(FW * GCW * STEP, FW * GCW * STEP),
class_bg_brush: brushes[bg],
..Default::default()
});
// WindowMain is based on Arc, so wnd.clone() is a shallow copy of a reference.
let context = Rc::new(RefCell::new(Context::new(wnd.clone())));
wnd.on().wm_paint({
let context = Rc::clone(&context);
move || {
let ctx = context.borrow();
let mut ps = winsafe::PAINTSTRUCT::default();
let hdc = ctx.wnd.hwnd().BeginPaint(&mut ps)?;
hdc.SelectObjectPen(HPEN::CreatePen(co::PS::NULL, 0, COLORREF::new(0, 0, 0))?)?;
for (&id, &brush) in ctx.r.iter().zip(&brushes[bg..=head]) {
let [left, top] = [id % TW, id / TW].map(|i| i * STEP - (STEP * GCW + SNAKE_W) / 2);
let rect = RECT { left, top, right: left + SNAKE_W, bottom: top + SNAKE_W };
hdc.SelectObjectBrush(brush)?;
hdc.RoundRect(rect, SIZE::new(SNAKE_W / 2, SNAKE_W / 2))?;
}
Ok(ctx.wnd.hwnd().EndPaint(&ps))
}
});
wnd.on().wm_key_down({
let context = Rc::clone(&context);
move |key| {
let mut ctx = context.borrow_mut();
Ok(match (ctx.incr.abs(), key.char_code as u8) {
(0, b' ') => _ = ctx.wnd.hwnd().SetTimer(1, 10, None)?, // Start / Restart
(TW, bt @ (b'A' | b'D')) => ctx.next_incr = if bt == b'A' { -1 } else { 1 },
(1, bt @ (b'S' | b'W')) => ctx.next_incr = if bt == b'S' { TW } else { -TW },
_ => (),
})
}
});
wnd.on().wm_timer(1, move || {
let mut ctx = context.borrow_mut();
let new_h = ctx.r[head] + ctx.incr;
(ctx.r[body], ctx.r[head]) = (ctx.r[head], new_h);
if ctx.gap < 0 {
ctx.r[bg] = ctx.snake.remove(0);
ctx.r[tail] = ctx.snake[0];
ctx.r[turn] = ctx.snake[GCW as usize / 2];
}
ctx.gap -= ctx.gap.signum();
if ctx.gap == 0 {
ctx.gap = if new_h == ctx.r[food] { GCW } else { -GCW };
let mut snake_cells: Vec<_> = ctx.snake.iter().step_by(GCW as usize).collect();
if new_h == ctx.r[food] {
ctx.wnd.hwnd().SetWindowText(&format!("Snake - Eaten: {}", snake_cells.len()))?;
snake_cells.sort();
ctx.r[food] = *(grid.iter())
.filter(|i| **i != new_h && snake_cells.binary_search(i).is_err())
.nth(thread_rng().gen_range(0..(grid.len() - 1 - snake_cells.len()).max(1)))
.unwrap_or(&0);
} else if grid.binary_search(&new_h).is_err() || snake_cells.contains(&&new_h) {
ctx.wnd.hwnd().KillTimer(1)?; // Stop
let title = ctx.wnd.hwnd().GetWindowText()?;
ctx.wnd.hwnd().SetWindowText(&(title + ". Restart: Space"))?;
*ctx = Context::new(ctx.wnd.clone());
return Ok(());
}
ctx.incr = ctx.next_incr;
}
ctx.snake.push(new_h);
ctx.wnd.hwnd().InvalidateRect(None, new_h == ID0)?; // call .wm_paint(), with erase on Restart
Ok(())
});
wnd.run_main(None).unwrap();
}
- Output:
Sidef
class SnakeGame(w, h) {
const readkey = frequire('Term::ReadKey')
const ansi = frequire('Term::ANSIColor')
enum (VOID, HEAD, BODY, TAIL, FOOD)
define (
LEFT = [+0, -1],
RIGHT = [+0, +1],
UP = [-1, +0],
DOWN = [+1, +0],
)
define BG_COLOR = "on_black"
define FOOD_COLOR = ("red" + " " + BG_COLOR)
define SNAKE_COLOR = ("bold green" + " " + BG_COLOR)
define SLEEP_SEC = 0.02
const (
A_VOID = ansi.colored(' ', BG_COLOR),
A_FOOD = ansi.colored('❇', FOOD_COLOR),
A_BLOCK = ansi.colored('■', SNAKE_COLOR),
)
has dir = LEFT
has grid = [[]]
has head_pos = [0, 0]
has tail_pos = [0, 0]
method init {
grid = h.of { w.of { [VOID] } }
head_pos = [h//2, w//2]
tail_pos = [head_pos[0], head_pos[1]+1]
grid[head_pos[0]][head_pos[1]] = [HEAD, dir] # head
grid[tail_pos[0]][tail_pos[1]] = [TAIL, dir] # tail
self.make_food()
}
method make_food {
var (food_x, food_y)
do {
food_x = w.rand.int
food_y = h.rand.int
} while (grid[food_y][food_x][0] != VOID)
grid[food_y][food_x][0] = FOOD
}
method display {
print("\033[H", grid.map { |row|
row.map { |cell|
given (cell[0]) {
when (VOID) { A_VOID }
when (FOOD) { A_FOOD }
default { A_BLOCK }
}
}.join('')
}.join("\n")
)
}
method move {
var grew = false
# Move the head
var (y, x) = head_pos...
var new_y = (y+dir[0] % h)
var new_x = (x+dir[1] % w)
var cell = grid[new_y][new_x]
given (cell[0]) {
when (BODY) { die "\nYou just bit your own body!\n" }
when (TAIL) { die "\nYou just bit your own tail!\n" }
when (FOOD) { grew = true; self.make_food() }
}
# Create a new head
grid[new_y][new_x] = [HEAD, dir]
# Replace the current head with body
grid[y][x] = [BODY, dir]
# Update the head position
head_pos = [new_y, new_x]
# Move the tail
if (!grew) {
var (y, x) = tail_pos...
var pos = grid[y][x][1]
var new_y = (y+pos[0] % h)
var new_x = (x+pos[1] % w)
grid[y][x][0] = VOID # erase the current tail
grid[new_y][new_x][0] = TAIL # create a new tail
tail_pos = [new_y, new_x]
}
}
method play {
STDOUT.autoflush(true)
readkey.ReadMode(3)
try {
loop {
var key
while (!defined(key = readkey.ReadLine(-1))) {
self.move()
self.display()
Sys.sleep(SLEEP_SEC)
}
given (key) {
when ("\e[A") { if (dir != DOWN ) { dir = UP } }
when ("\e[B") { if (dir != UP ) { dir = DOWN } }
when ("\e[C") { if (dir != LEFT ) { dir = RIGHT } }
when ("\e[D") { if (dir != RIGHT) { dir = LEFT } }
}
}
}
catch {
readkey.ReadMode(0)
}
}
}
var w = `tput cols`.to_i
var h = `tput lines`.to_i
SnakeGame(w || 80, h || 24).play
UNIX Shell
Play ASCII snake in your ANSI terminal.
It theoretically should also work with the Korn Shell, but for some reason the timeout on the key read is not consistent and the snake stops moving except one step after each keypress.
function main {
typeset -i game_over=0
typeset -i height=$(tput lines) width=$(tput cols)
# start out in the middle moving to the right
typeset -i dx dy hx=$(( width/2 )) hy=$(( height/2 ))
typeset -a sx=($hx) sy=($hy)
typeset -a timeout
clear
tput cup "$sy" "$sx" && printf '@'
tput cup $(( height/2+2 )) 0
center $width "Press h, j, k, l to move left, down, up, right"
# place first food
typeset -i fx=hx fy=hy
while (( fx == hx && fy == hy )); do
fx=$(( RANDOM % (width-2)+1 )) fy=$(( RANDOM % (height-2)+1 ))
done
tput cup "$fy" "$fx" && printf '*'
# handle variations between shells
keypress=(-N 1) origin=0
if [[ -n $ZSH_VERSION ]]; then
keypress=(-k)
origin=1
fi
stty -echo
tput civis
typeset key
read "${keypress[@]}" -s key
typeset -i start_time=$(date +%s)
tput cup "$(( height/2+2 ))" 0 && tput el
while (( ! game_over )); do
timeout=(-t $(printf '0.%04d' $(( 2000 / (${#sx[@]}+1) )) ) )
if [[ -z $key ]]; then
read "${timeout[@]}" "${keypress[@]}" -s key
fi
case "$key" in
h) if (( dx != 1 )); then dx=-1; dy=0; fi;;
j) if (( dy != -1 )); then dy=1; dx=0; fi;;
k) if (( dy != 1 )); then dy=-1; dx=0; fi;;
l) if (( dx != -1 )); then dx=1; dy=0; fi;;
q) game_over=1; tput cup 0 0 && print "Final food was at ($fx,$fy)";;
esac
key=
(( hx += dx, hy += dy ))
# if we try to go off screen, game over
if (( hx < 0 || hx >= width || hy < 0 || hy >= height )); then
game_over=1
else
# if we run into ourself, game over
for (( i=0; i<${#sx[@]}; ++i )); do
if (( hx==sx[i+origin] && hy==sy[i+origin] )); then
game_over=1
break
fi
done
fi
if (( game_over )); then
break
fi
# add new spot
sx+=($hx) sy+=($hy)
if (( hx == fx && hy == fy )); then
# if we just ate some food, place some new food out
ok=0
while (( ! ok )); do
# make sure we don't put it under ourselves
ok=1
fx=$(( RANDOM % (width-2)+1 )) fy=$(( RANDOM % (height-2)+1 ))
for (( i=0; i<${#sx[@]}; ++i )); do
if (( fx == sx[i+origin] && fy == sy[i+origin] )); then
ok=0
break
fi
done
done
tput cup "$fy" "$fx" && printf '*'
# and don't remove our tail because we've just grown by 1
else
# if we didn't just eat food, remove our tail from its previous spot
tput cup ${sy[origin]} ${sx[origin]} && printf ' '
sx=( ${sx[@]:1} )
sy=( ${sy[@]:1} )
fi
# draw our new head
tput cup "$hy" "$hx" && printf '@'
done
typeset -i end_time=$(date +%s)
tput cup $(( height / 2 -1 )) 0 && center $width 'GAME OVER'
tput cup $(( height / 2 )) 0 &&
center $width 'Time: %d seconds' $(( end_time - start_time ))
tput cup $(( height / 2 + 1 )) 0 &&
center $width 'Final length: %d' ${#sx[@]}
echo
stty echo
tput cnorm
}
function center {
typeset -i width=$1 i
shift
typeset message=$(printf "$@")
tput cuf $(( (width-${#message}) / 2 ))
printf '%s' "$message"
}
main "$@"
Wren
An embedded program so we can ask the C host to call ncurses and another library function for us.
/* Snake.wren */
import "random" for Random
import "./dynamic" for Enum, Lower
foreign class Window {
construct initscr() {}
foreign nodelay(bf)
}
class Ncurses {
foreign static cbreak()
foreign static noecho()
foreign static refresh()
foreign static getch()
foreign static mvaddch(y, x, ch)
foreign static endwin()
}
class C {
foreign static usleep(usec)
}
var Dir = Enum.create("Dir", ["N", "E", "S", "W"])
var State = Enum.create("State", ["space", "food", "border"])
var w = 80
var h = 40
var board = List.filled(w * h, 0)
var rand = Random.new()
var head
var dir
var quit
// ASCII values
var hash = 35
var at = 64
var dot = 46
var spc = 32
/* negative values denote the snake (a negated time-to-live in given cell) */
// reduce a time-to-live, effectively erasing the tail
var age = Fn.new {
for (i in 0...w * h) {
if (board[i] < 0) board[i] = board[i] + 1
}
}
// put a piece of food at random empty position
var plant = Fn.new {
var r
while (true) {
r = rand.int(w * h)
if (board[r] = State.space) break
}
board[r] = State.food
}
// initialize the board, plant a very first food item
var start = Fn.new {
for (i in 0...w) board[i] = board[i + (h - 1) * w] = State.border
for (i in 0...h) board[i * w] = board[i * w + w - 1] = State.border
head = (w * (h - 1 - h % 2) / 2).floor // screen center for any h
board[head] = -5
dir = Dir.N
quit = false
plant.call()
}
var step = Fn.new {
var len = board[head]
if (dir == Dir.N) {
head = head - w
} else if (dir == Dir.S) {
head = head + w
} else if (dir == Dir.W) {
head = head - 1
} else if (dir == Dir.E) {
head = head + 1
}
if (board[head] == State.space) {
board[head] = len - 1 // keep in mind len is negative
age.call()
} else if (board[head] == State.food) {
board[head] = len - 1
plant.call()
} else {
quit = true
}
}
var show = Fn.new {
var symbol = [spc, at, dot]
for (i in 0...w*h) {
Ncurses.mvaddch((i/w).floor, i % w, board[i] < 0 ? hash : symbol[board[i]])
}
Ncurses.refresh()
}
var initScreen = Fn.new {
var win = Window.initscr()
Ncurses.cbreak()
Ncurses.noecho()
win.nodelay(true)
}
initScreen.call()
start.call()
while (true) {
show.call()
var ch = Ncurses.getch()
if (ch == Lower.i) {
dir = Dir.N
} else if (ch == Lower.j) {
dir = Dir.W
} else if (ch == Lower.k) {
dir = Dir.S
} else if (ch == Lower.l) {
dir = Dir.E
} else if (ch == Lower.q) {
quit = true
}
step.call()
C.usleep(300 * 1000) // 300 ms is a reasonable delay
if (quit) break
}
C.usleep(999 * 1000)
Ncurses.endwin()
Now embed this script in the following C program, compile and run it.
/* gcc Snake.c -o Snake -lncurses -lwren -lm */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ncurses.h>
#include <unistd.h>
#include "wren.h"
/* C <=> Wren interface functions */
void C_windowAllocate(WrenVM* vm) {
WINDOW** pwin = (WINDOW**)wrenSetSlotNewForeign(vm, 0, 0, sizeof(WINDOW*));
*pwin = initscr();
}
void C_nodelay(WrenVM* vm) {
WINDOW* win = *(WINDOW**)wrenGetSlotForeign(vm, 0);
bool bf = wrenGetSlotBool(vm, 1);
nodelay(win, bf);
}
void C_cbreak(WrenVM* vm) {
cbreak();
}
void C_noecho(WrenVM* vm) {
noecho();
}
void C_refresh(WrenVM* vm) {
refresh();
}
void C_getch(WrenVM* vm) {
int ch = getch();
wrenSetSlotDouble(vm, 0, (double)ch);
}
void C_mvaddch(WrenVM* vm) {
int y = (int)wrenGetSlotDouble(vm, 1);
int x = (int)wrenGetSlotDouble(vm, 2);
const chtype ch = (const chtype)wrenGetSlotDouble(vm, 3);
mvaddch(y, x, ch);
}
void C_endwin(WrenVM* vm) {
endwin();
}
void C_usleep(WrenVM* vm) {
useconds_t usec = (useconds_t)wrenGetSlotDouble(vm, 1);
usleep(usec);
}
WrenForeignClassMethods bindForeignClass(WrenVM* vm, const char* module, const char* className) {
WrenForeignClassMethods methods;
methods.allocate = NULL;
methods.finalize = NULL;
if (strcmp(module, "main") == 0) {
if (strcmp(className, "Window") == 0) {
methods.allocate = C_windowAllocate;
}
}
return methods;
}
WrenForeignMethodFn bindForeignMethod(
WrenVM* vm,
const char* module,
const char* className,
bool isStatic,
const char* signature) {
if (strcmp(module, "main") == 0) {
if (strcmp(className, "Window") == 0) {
if (!isStatic && strcmp(signature, "nodelay(_)") == 0) return C_nodelay;
} else if (strcmp(className, "Ncurses") == 0) {
if (isStatic && strcmp(signature, "cbreak()") == 0) return C_cbreak;
if (isStatic && strcmp(signature, "noecho()") == 0) return C_noecho;
if (isStatic && strcmp(signature, "refresh()") == 0) return C_refresh;
if (isStatic && strcmp(signature, "getch()") == 0) return C_getch;
if (isStatic && strcmp(signature, "mvaddch(_,_,_)") == 0) return C_mvaddch;
if (isStatic && strcmp(signature, "endwin()") == 0) return C_endwin;
} else if (strcmp(className, "C") == 0) {
if (isStatic && strcmp(signature, "usleep(_)") == 0) return C_usleep;
}
}
return NULL;
}
static void writeFn(WrenVM* vm, const char* text) {
printf("%s", text);
}
void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {
switch (errorType) {
case WREN_ERROR_COMPILE:
printf("[%s line %d] [Error] %s\n", module, line, msg);
break;
case WREN_ERROR_STACK_TRACE:
printf("[%s line %d] in %s\n", module, line, msg);
break;
case WREN_ERROR_RUNTIME:
printf("[Runtime Error] %s\n", msg);
break;
}
}
char *readFile(const char *fileName) {
FILE *f = fopen(fileName, "r");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
rewind(f);
char *script = malloc(fsize + 1);
fread(script, 1, fsize, f);
fclose(f);
script[fsize] = 0;
return script;
}
static void loadModuleComplete(WrenVM* vm, const char* module, WrenLoadModuleResult result) {
if( result.source) free((void*)result.source);
}
WrenLoadModuleResult loadModule(WrenVM* vm, const char* name) {
WrenLoadModuleResult result = {0};
if (strcmp(name, "random") != 0 && strcmp(name, "meta") != 0) {
result.onComplete = loadModuleComplete;
char fullName[strlen(name) + 6];
strcpy(fullName, name);
strcat(fullName, ".wren");
result.source = readFile(fullName);
}
return result;
}
int main(int argc, char **argv) {
WrenConfiguration config;
wrenInitConfiguration(&config);
config.writeFn = &writeFn;
config.errorFn = &errorFn;
config.bindForeignClassFn = &bindForeignClass;
config.bindForeignMethodFn = &bindForeignMethod;
config.loadModuleFn = &loadModule;
WrenVM* vm = wrenNewVM(&config);
const char* module = "main";
const char* fileName = "Snake.wren";
char *script = readFile(fileName);
WrenInterpretResult result = wrenInterpret(vm, module, script);
switch (result) {
case WREN_RESULT_COMPILE_ERROR:
printf("Compile Error!\n");
break;
case WREN_RESULT_RUNTIME_ERROR:
printf("Runtime Error!\n");
break;
case WREN_RESULT_SUCCESS:
break;
}
wrenFreeVM(vm);
free(script);
return 0;
}
XPL0
XPL0, like implementations of BASIC on early personal computers, was designed to support video games like this. The 40x25 character color screen is a display mode built into the IBM-PC and simulated by XPL0 in the Raspberry Pi and Windows (EXPL) versions.
The initial length of the snake and the number of food items constantly available can be changed in the defined constants at the beginning. The speed is regulated by the duration of the Sound routine (intrinsic), which has a granularity of one DOS system tick, or about 1/18 second. The speed is currently set at nine steps per second.
def Width=40, Height=25-1, \playing area including border
StartLength = 10, \starting length of snake including head
Morsels = 10; \number of food items constantly offered
int Heading; \direction snake is heading
def Up, Down, Left, Right;
int Length, \current length of snake including head
Score; \number of food items eaten
char SnakeX(10000), \location of each segment including head
SnakeY(10000), \ample room to grow
FoodX(Morsels), FoodY(Morsels); \location of each food item
def Black, Blue, Green, Cyan, Red, Magenta, Brown, White, \attribute colors
Gray, LBlue, LGreen, LCyan, LRed, LMagenta, Yellow, BWhite; \EGA palette
proc PlaceFood(N); \Place Nth food item in playing area
int N;
[FoodX(N):= Ran(Width-3) + 1; \pick random location inside borders
FoodY(N):= Ran(Height-3) + 1;
Cursor(FoodX(N), FoodY(N)); \show food
Attrib(Red<<4+LRed);
ChOut(6, ^@);
]; \PlaceFood
int X, Y, I, C;
[SetVid($01); \set 40x25 text mode
ShowCursor(false); \turn off flashing cursor
Attrib(Blue<<4+LBlue); \show borders
Cursor(0, 0);
for X:= 0 to Width-1 do ChOut(6, ^+);
Cursor(0, Height-1);
for X:= 0 to Width-1 do ChOut(6, ^+);
for Y:= 0 to Height-1 do
[Cursor(0, Y); ChOut(6, ^+);
Cursor(Width-1, Y); ChOut(6, ^+);
];
Attrib(Black<<4+White); \show initial score
Cursor(0, 24);
Text(6, "Score: 0");
Score:= 0;
SnakeX(0):= Width/2; \start snake head at center
SnakeY(0):= Height/2;
Heading:= Left;
Length:= StartLength;
for I:= 1 to Length-1 do \segments follow head to the right
[SnakeX(I):= SnakeX(I-1) + 1;
SnakeY(I):= SnakeY(I-1);
];
for I:= 0 to Morsels-1 do PlaceFood(I); \sow some tasty food
\Continuously move snake
loop \--------------------------------------------------------------------------
[Attrib(Black<<4+White); \remove tail-end segment
Cursor(SnakeX(Length-1), SnakeY(Length-1));
ChOut(6, ^ );
Attrib(Green<<4+Yellow); \add segment at head location
Cursor(SnakeX(0), SnakeY(0));
ChOut(6, ^#);
\Shift coordinates toward tail (+1 in case a segment gets added)
for I:= Length downto 1 do \segment behind head gets head's coords
[SnakeX(I):= SnakeX(I-1);
SnakeY(I):= SnakeY(I-1);
];
if ChkKey then \key hit--get new movement direction
[repeat C:= ChIn(1) until C # 0; \remove arrow keys' prefix byte
case C of
$1B: exit; \Escape, and scan codes for arrow keys
$48: if Heading # Down then Heading:= Up;
$50: if Heading # Up then Heading:= Down;
$4B: if Heading # Right then Heading:= Left;
$4D: if Heading # Left then Heading:= Right
other []; \ignore any other keystrokes
];
case Heading of \move head to its new location
Up: SnakeY(0):= SnakeY(0)-1;
Down: SnakeY(0):= SnakeY(0)+1;
Left: SnakeX(0):= SnakeX(0)-1;
Right:SnakeX(0):= SnakeX(0)+1
other [];
Cursor(SnakeX(0), SnakeY(0)); \show head at its new location
ChOut(6, ^8);
for I:= 0 to Morsels-1 do
if SnakeX(0)=FoodX(I) & SnakeY(0)=FoodY(I) then
[Score:= Score+1; \ate a morsel
Attrib(Black<<4+White);
Cursor(7, 24);
IntOut(6, Score*10);
PlaceFood(I); \replenish morsel
Length:= Length+1; \grow snake one segment
I:= Morsels; \over eating can be bad--quit for loop
Sound(1, 1, 1500); \BURP!
Sound(1, 1, 1000);
];
if I = Morsels then Sound(0, 2, 1); \no sound adds delay of 2/18th second
if SnakeX(0)=0 or SnakeX(0)=Width-1 or
SnakeY(0)=0 or SnakeY(0)=Height-1 then
quit; \snake hit border--game over
for I:= 1 to Length-1 do
if SnakeX(0)=SnakeX(I) & SnakeY(0)=SnakeY(I) then
quit; \snake bit itself--game over
]; \loop -----------------------------------------------------------------------
for I:= 1 to 8 do Sound(1, 1, 4000*I); \death dirge
Sound(0, 36, 1); \pause 2 seconds to see result
OpenI(1); \flush any pending keystrokes
]
- Programming Tasks
- Games
- WikipediaSourced
- Ada
- Amazing Hopper
- AutoHotkey
- BASIC
- Applesoft BASIC
- Craft Basic
- FreeBASIC
- Integer BASIC
- Locomotive Basic
- ZX Spectrum Basic
- C
- C++
- Delphi
- Winapi.Windows
- System.SysUtils
- System.Classes
- Vcl.Graphics
- Vcl.Forms
- Vcl.Dialogs
- System.Generics.Collections
- Vcl.ExtCtrls
- EasyLang
- Emacs Lisp
- F Sharp
- Go
- Haskell
- J
- Java
- JavaScript
- Julia
- Kotlin
- Lua
- Pages with broken file links
- Nim
- Nim-ncurses
- OCaml
- OCamlSDL2
- Perl
- Phix
- Python
- Raku
- Rust
- Sidef
- UNIX Shell
- Wren
- Ncurses
- Wren-dynamic
- XPL0