19
\$\begingroup\$

The challenge is to create the classic Snake game using as few bytes as possible.

Here are the requirements:

  • The game must be implemented in a typical 2-dimensional layout. The snake should be able to grow significantly within the bounds of the map (this really means, don't make your map too small, use your discretion here).
  • A user may move the snake using keys of your choosing, however, the snake cannot double back on itself (e.g. if it is going West it cannot go East without first going North or South). A snake ought to be able to travel in all 4 directions: up, down, left, right (North, South, West, East).
  • Snake starts off as length 1, each time it eats a "food" object it grows +1 in length
  • Food objects are randomly placed in locations other than those occupied by the snake
  • If the Snake hits itself or a wall the game is ended
  • When the game has been ended the literal "Score: [score]" is displayed where [score] is the number of food items eaten during the game. So, for example, if the snake has eaten 4 "foods" (and therefore has a length of 5) when the game ends, "Score: 4" will be printed.
  • No compression algorithms unless they are explicitly defined in your code.

Here's my solution, 908 Bytes, Python 2.7

import random as r
import curses as c
def g(s,w,l):
 while 1:
  p=[r.randrange(0,w),r.randrange(0,l)]
  for l in s:
   if l==p:continue
  return p
s=[]
d=[0,1]
p=k=n=0
e=100
v={65:[-1,0],66:[1,0],68:[0,-1],67:[0,1]}
z=c.initscr()
w,l=z.getmaxyx()[0],z.getmaxyx()[1]
c.noecho()
z.clear()
x=g(s,w,l)
s.append([w/2,l/2])
z.nodelay(1)
q=lambda h,i:range(h,len(i))
while k!=101:
 k=z.getch()
 if k in v and not (d[0]==(v[k][0]*-1) and d[1]==(v[k][1]*-1)):d=v[k]
 f=[0,0]
 for i in q(0,s):
  if i == 0:
   f=[s[i][0],s[i][1]]
   s[i][0]+=d[0]
   s[i][1]+=d[1]
  else:s[i],f=f,s[i]
 if s[0]==x:
  n+=1
  s.append(f)
  x=g(s,w,l)
 z.clear()
 if s[0][0]>=w or s[0][1]>=l or s[0][0]<0 or s[0][1]<0:break
 for i in q(1,s):
  if s[0] == s[i]: k = 101
 for i in q(0,s):z.addch(s[i][0],s[i][1],"X")
 z.addch(x[0],x[1],"O")
 z.move(0,0)
 z.refresh()
 if d[1]!=0:c.napms(e/2)
 else:c.napms(e)
c.endwin()
print 'Score: %s'%n
\$\endgroup\$
4
  • 1
    \$\begingroup\$ possible duplicate of Recreate a 'Snake' game in a console/terminal \$\endgroup\$
    – copy
    Commented Sep 8, 2012 at 18:23
  • 1
    \$\begingroup\$ @copy some people don't like to be restricted to terminals. \$\endgroup\$
    – Griffin
    Commented Sep 8, 2012 at 18:29
  • \$\begingroup\$ does the 'snake cannot double back' rule apply if the snake is length=1? \$\endgroup\$ Commented Sep 10, 2012 at 4:55
  • \$\begingroup\$ @chron, yes it does. At all times, snakes may only (truly) turn two ways, left and right. \$\endgroup\$
    – mjgpy3
    Commented Sep 10, 2012 at 11:09

6 Answers 6

4
\$\begingroup\$

APL (dzaima/APL), 244 233 bytes

s←2⌿⊂2⌿25
d←?x←2⌿50
o←⍳2
G←P5.G
P5.draw←{G.bg¯1
G.fill←0
s⊢←(1↓s),⊂o+f←⊃⌽s
((⊢≢∪)s)∨~f⊂⍛∊a←⍳x:P5.exit 0⊣⍞←'Score: ',⍕2-⍨≢s
d≡f:s⊢←s,⊂f+2×o⊣d⊢←?x
(t←'center'G.rect,∘2 2)¨2×s
G.fill←'f00'
t+⍨d}
P5.kp←{o≢-n←('sdwa'⍳⍵)⊃1-↓⍬2⍴3⊤2320:o⊢←n}

Golfed from 294 → 244 bytes with the help of dzaima.

Uses WASD controls.

Uses ⎕IO←0 (0-indexing).

The grid is 50x50, on the default 100x100 canvas Processing uses.

Displays score to the console.

Explanation done!

Explanation

s←2⌿⊂2⌿25 snake starts at \$(25,25)\$

d←?x←2⌿50 first food position: 2 random ints in (0,50)

x←2⌿50 save (50,50) in x

o←⍳2 initial snake direction: (0,1)

G←P5.G save namespace as G

P5.draw←{ execute the following each frame:

G.bg¯1 display white background

G.fill←0 set fill to black

s⊢←(1↓s),⊂o+f←⊃⌽s calculate next snake position:

(1↓s) remove the last tail segment

,⊂o+f←⊃⌽s add direction to the front, and append that

f←⊃⌽s assign head coordinates to f

((⊢≢∪)s)∨~f⊂⍛∊a←⍳x:(...) check if the player has lost:

((⊢≢∪)s)∨ if snake does not match itself uniquified, or

~f⊂⍛∊a←⍳x: head is not within 50x50 grid:

⍞←'Score: ',⍕2-⍨≢s display the score in console, and

P5.exit 0⊣ exit, displaying nothing

d≡f:s⊢←s,⊂f+2×o⊣d⊢←?x if head matches food, then change food position and increase snake length.

(t←'center'G.rect,∘2 2)¨2×s draw each position of the snake

t←'center'G.rect,∘2 2 create function t for drawing the blocks

G.fill←'f00' set fill to red`

t+⍨d draw the food

} close draw loop

P5.kp←{...} execute the following each time a key is pressed (⍵ is key name):

1-↓⍬2⍴3⊤2320 array of directions │0 1│1 0│0 ¯1│¯1 0│

('sdwa'⍳⍵)⊃ get key number, index into directions

n← store direction in n

o≢- if snake direction is not the opposite of n,(prevents moving backward)

o⊢←n then change snake direction

Gameplay

(recorded at low fps)

enter image description here

\$\endgroup\$
2
  • \$\begingroup\$ The tio link reports errors for me. \$\endgroup\$
    – user7467
    Commented Dec 13, 2020 at 10:22
  • \$\begingroup\$ @Anush the tio link isn't supposed to run it, it needs to be run on your system. I left that in by mistake. \$\endgroup\$
    – Razetime
    Commented Dec 13, 2020 at 14:27
4
\$\begingroup\$

JavaScript, 609 bytes

document.write`<canvas id=C width=40 height=40 style='width:180px;height:180px;border:4px solid'`
s=[l={x:9,y:9}]
h=t=0,v=1
r=(x,y,w,h,f)=>{c.fillStyle=f,c.fillRect(x,y,w,h)}
m=n=>{g=4,i=0|38*(R=Math.random)()+1,j=0|38*R()+1,s.map(b=>b.x^i|b.y^j?0:m())},m()
onkeydown=k=>{f={d:[0,1],a:[0,-1],w:[-1,0],s:[1,0]}[k.key],f?(v=f[0],h=f[1]):0}
z=setInterval(w=>z?(focus(),c=C.getContext`2d`,r(0,0,80,80,'#fff'),s.push(l={x:l.x+h,y:l.y+v}),s.splice(0,0>g--),l.x^i|l.y^j?0:t+=!m(),s.map(b=>z&&(0>l.x||0>l.y||39<l.x||39<l.y||b!=l&&b.x==l.x&&b.y==l.y)?z=alert('Score:'+t):r(b.x,b.y,1,1,'#000')),r(i,j,1,1,'#f0f')):0,75)

Use WASD for controls.

JSFiddle

\$\endgroup\$
5
  • 2
    \$\begingroup\$ You can remove all the windows and replace the addEventListener with onkeydown=... \$\endgroup\$
    – rydwolf
    Commented Dec 9, 2020 at 15:01
  • 1
    \$\begingroup\$ Also, parseInt(...) is way longer than ...|0, and you might be able to save some bytes by replacing document with (D=document and then D. Other than that this is a really cool answer! \$\endgroup\$
    – rydwolf
    Commented Dec 9, 2020 at 15:06
  • \$\begingroup\$ @Redwolf, cool thanks! I'll update it a bit later, first have to get some actual work done ;-) \$\endgroup\$
    – jdt
    Commented Dec 9, 2020 at 15:11
  • 1
    \$\begingroup\$ -13 bytes: pastebin.com/3xZtyEH2 \$\endgroup\$
    – user92753
    Commented Dec 11, 2020 at 22:20
  • \$\begingroup\$ Bugs: Trying to double the snake back on itself ends the game, and there is a missing space between Score: and the score \$\endgroup\$
    – Makonede
    Commented May 26, 2021 at 23:45
3
\$\begingroup\$

Ruby 1.9 + SDL (341 324 316)

Here's a first attempt at a Ruby version using the SDL library. I can save 6 characters if I'm allowed to load the SDL library using -rsdl on the command line instead of the require statement.

require'sdl'
f=o=d=3
s=SDL::Screen.open l=32,l,0,0
r=*0..l*l
loop{f==o ?f=(r-$*).sample: $*.shift
/yU/=~"#{e=SDL::Event.poll}"&&(v=e.sym%4)&&d+v!=3&&d=v
$><<"Score #{$*.size}"&&exit if$*.index(n=o+[-1,-l,l,1][d])||n<0||n>=l*l||d%3<1&&n/l!=o/l
$*<<o=n
r.map{|i|s[i%l,i/l]=[[f,*$*].index(i)?0:255]*3}
s.flip
sleep 0.1}

The snake segments and food pieces are represented using black pixels, the grid size is currently 32*32. You can control with the arrow keys (or any keys really, the keycode mod 4 indexes the direction array [LEFT, UP, DOWN, RIGHT]). I think there's definitely room for improvement here, especially in the death-checking IF statement.

I've vastly improved this over the previous version, hopefully it more closely matches the spirit of the question now. There's one thing I need to fix to comply with the spec, which is that food can currently spawn inside the tail. Fixed!

Prints the score to stdout after the game is completed.

\$\endgroup\$
3
\$\begingroup\$

Bash: 537 533 507 characters

C=$COLUMNS;L=$LINES;D=-1;c=9;r=9;z=(9\ 9);l=;h=1;v=;s=1;d=1
t(){ echo -en "\e[$2;$1H$3";}
b(){ ((f=RANDOM%C+1));((g=RANDOM%L+1));for i in "${z[@]}";do [[ $f\ $g = $i ]]&&b;done;t $f $g F;}
echo $'\e[2J';b
while :;do
read -sn1 -t.1 k
case $k in
w|s)((h))&&h=&&v=${D:$k};;
a|d)((v))&&v=&&h=${D:$k};;
esac
((c+=h));((r+=v))
((c==f&&r==g&&++l))&&b
((c<1||r<1||c>C||r>L))&&break
for i in "${z[@]}";do [[ $c\ $r = $i ]]&&break 2;done
t ${z[-1]} \ ;t $c $r X
z=($c\ $r "${z[@]::l}")
done
echo $'\e[2J\e[H'Score: $l

As it uses the $COLUMNS and $LINES shell variables, it must be run sourced: . snake.sh. The snake can be controlled with the w/a/s/d keys.

I know, it can be easily reduced to 493 characters by using clear to clear the screen, but I prefer to keep it pure bash, without using any external tool.

\$\endgroup\$
2
  • \$\begingroup\$ Very cool solution! \$\endgroup\$
    – mjgpy3
    Commented Sep 11, 2012 at 18:03
  • \$\begingroup\$ I love bash solutions! \$\endgroup\$
    – user7467
    Commented Dec 13, 2020 at 10:22
3
\$\begingroup\$

Python 2.7: 869 816 818 817 816 Characters

I hacked this together in the last few hours. It should meet the requirements and is a few characters shorter than mjgpy3's solution (Tried hard, but couldn't get it much shorter. Now I'm tired). Surprisingly, using a game development library like pygame didn't get the python-snake much shorter. Suggestions and tips how to make it shorter are highly appreciated. I hope it's not too cryptic.

This is the result:

import pygame as p
from random import randint as r
p.init();l=20
c=p.time.Clock()
dp=p.display;w=p.display.set_mode((500,)*2)
C=p.Color;b=C(0,0,0);g=C(0,99,0)
D=(0,1);U=(0,-1);L=(-1,0);R=(1,0)
S=[R];d=R;n=[]
O=lambda t:{U:D,R:L,D:U,L:R}[t]
def Q(e):print "Score: %i"%(len(S)-1);p.quit()
def K(e):global d;_={276:L,273:U,274:D,275:R}.get(e.key,(0,0));d=not _==O(d) and _ or d
def N(S):[p.draw.rect(w,g,[x[0]*l,x[1]*l,l,l]) for x in S+n] 
def M():n=(r(0,24),r(0,24));return n not in S and n or M()
A=lambda s,o:tuple(x+y for x,y in zip(s,o))
n=[M()] 
while True:
 w.fill(b);[{12:Q,2:K}.get(e.type,lambda e:e)(e) for e in p.event.get()]
 if not (0<=S[-1][0]<25 and 0<=S[-1][1]<25) or A(S[-1],d) in S: Q(e) 
 if A(S[-1],d) in n: S.append(A(S[-1],d));n=[M()]
 else: S.append(A(S[-1],d));S.pop(0)
 N(S);dp.update();c.tick(6)

EDIT: I could reduce it to 816 Bytes, yay! :) Fixed the score

EDIT2: Pasted the wrong version accidentally

Here is a commented version:

import pygame as p
from random import randint as r

# initialize pygame
p.init()

# the game consists of 25*25 blocks,with each block 20*20 pixels
l=20

# initialize the main loop clock
c=p.time.Clock()

# open the window
dp=p.display;w=p.display.set_mode((500,)*2)

# define black and green colors
C=p.Color;b=C(0,0,0);g=C(0,99,0)

# Directions of the snake: down, up, left, right
D=(0,1);U=(0,-1);L=(-1,0);R=(1,0)

# S is the snake, d is the current direction and n is the array of foods
S=[R];d=R;n=[]

# get the opposite direction of a direction to forbid double backing
O=lambda t:{U:D,R:L,D:U,L:R}[t]

# print the score and quit
def Q(e):print "Score: %i"%(len(S)-1);p.quit()

# update the direction (this is a key press handler)
def K(e):global d;_={276:L,273:U,274:D,275:R}.get(e.key,(0,0));d=not _==O(d) and _ or d

# draw the snake and food boxes
def N(S):[p.draw.rect(w,g,[x[0]*l,x[1]*l,l,l]) for x in S+n]

# place new food on the map not colliding with the snake
def M():n=(r(0,24),r(0,24));return n not in S and n or M()

# A((1,1), (-2, 1)) -> (-1,2)
A=lambda s,o:tuple(x+y for x,y in zip(s,o))

# initialize food array
n=[M()]

while True:
 # fill the screen black
 w.fill(b)
 # get quit or key press events and execute the event handlers
 [{12:Q,2:K}.get(e.type,lambda e:e)(e) for e in p.event.get()]

 # check if snake hits map boundaries or itself
 if not (0<=S[-1][0]<25 and 0<=S[-1][1]<25) or A(S[-1],d) in S: Q(e)

 # check if snake is eating food at the moment and append one to the snake's length
 if A(S[-1],d) in n: S.append(A(S[-1],d));n=[M()]

 # move the snake in the current direction
 else: S.append(A(S[-1],d));S.pop(0)

 # draw the map and limit the main loop to 6 frames per second
 N(S);dp.update();c.tick(6)
\$\endgroup\$
4
  • \$\begingroup\$ I kept getting this error message "Segmentation fault (core dumped)." And it seems like the score is off by 1 (not really a big deal. Very cool answer though. \$\endgroup\$
    – mjgpy3
    Commented Sep 24, 2012 at 11:17
  • 2
    \$\begingroup\$ Thanks :) I get that Segmentation fauklt message too. Didn't figure it out yet. Fixed the score and reduced the size :) this is fun. \$\endgroup\$
    – stefreak
    Commented Sep 24, 2012 at 11:30
  • 1
    \$\begingroup\$ you could make the green darker, instead of 255, use 99, then that'll be a byte taken off \$\endgroup\$ Commented Mar 21, 2019 at 1:47
  • \$\begingroup\$ @KrystosTheOverlord hahah yes good point :D \$\endgroup\$
    – stefreak
    Commented Mar 22, 2019 at 19:08
2
\$\begingroup\$

Java, 2343 2239

Not exactly concise, but I believe it follows all the requirements.

Snake class

import javax.swing.*;
public class S extends JFrame{
S(){add(new B());setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setSize(320,340);setVisible(true);}
public static void main(String[]a){new S();}}

Board class

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class B extends JPanel implements ActionListener{
int W=300;int H=300;int DS=10;int AD=900;int RP=29;int D=140;int x[]=new int[AD];int y[]=new int[AD];int d;int ax;int ay;boolean l=false;boolean r=true;boolean u=false;boolean dn=false;boolean ig=true;Timer t;Image b;Image a;Image h;
B(){addKeyListener(new T());setBackground(Color.black);ImageIcon id=new ImageIcon(this.getClass().getResource("d.png"));b=id.getImage();ImageIcon ia=new ImageIcon(this.getClass().getResource("a.png"));a=ia.getImage();ImageIcon ih=new ImageIcon(this.getClass().getResource("h.png"));h=ih.getImage();setFocusable(true);i();}
void i(){d=3;for(int z=0;z<d;z++){x[z]=50-z*10;y[z]=50;}l();t=new Timer(D,this);t.start();}
public void p(Graphics g){super.paint(g);if(i){g.drawImage(a,ax,ay,this);for(int z=0;z<d;z++){if(z==0)g.drawImage(h,x[z],y[z],this);else g.drawImage(b,x[z],y[z],this);}Toolkit.getDefaultToolkit().sync();g.dispose();}else{g(g);}}
void g(Graphics g){String ms="Score:";Font sm=new Font("Courier",Font.PLAIN,12);FontMetrics me=this.getFontMetrics(sm);g.setColor(Color.white);g.setFont(sm);g.drawString(ms+d,(W-me.stringWidth(ms)),H);}
void c(){if((x[0]==ax)&&(y[0]==ay)){d++;l();}}
void m(){for(int z=d;z>0;z--){x[z]=x[(z-1)]; y[z]=y[(z-1)];}if(l){x[0]-=DS;}if (r){x[0]+=DS;}if(u){y[0]-=DS;}if(dn){y[0]+=DS;}}
void cc(){for(int z=d;z>0;z--){if((z>4)&&(x[0]==x[z])&&(y[0]==y[z])){ig=false;}}if(y[0]>H){ig=false;}if(y[0]<0){ig=false;}if(x[0]> W){ig=false;}if(x[0]<0){ig=false;}}
void l(){int r=(int)(Math.random()*RP);ax=((r*DS));r=(int)(Math.random()*RP);ay=((r*DS));}
public void actionPerformed(ActionEvent e){if(ig){c();cc();m();}repaint();}
class T extends KeyAdapter{public void keyPressed(KeyEvent e){int k=e.getKeyCode();if((k==KeyEvent.VK_LEFT)&&(!r)){l=true;u=false;dn=false;}if((k==KeyEvent.VK_RIGHT)&&(!l)){r=true;u=false;dn=false;}if((k==KeyEvent.VK_UP)&&(!dn)){u=true;r=false;l=false;}if((k==KeyEvent.VK_DOWN)&&(!u)){dn=true;r=false;l=false;}}}}

Screenshot

snake game in java


Commentary

A while back I visited a website called zetcode which provided some tutorials for creating classic 2D games in Java. The code provided is strongly influenced by the tutorial that was provided for the Snake game... I think at this time I just started coding classic games and followed the tutorial to a 'T'.

I'll make an edit later and add a link to an executable so people can play the game.


EDITS

  • 9/9/12: I am unable to properly load the images from the resource folder. I'll continue to work through this issue in an attempt to prove that my code works and meets all criteria of the question.
  • 9/11/12: I am going to continue working on getting the pictures to load from the resource file. I added a picture provided from the ZetCode tutorial.
\$\endgroup\$
3
  • \$\begingroup\$ Great, I look forward to trying it! \$\endgroup\$
    – mjgpy3
    Commented Sep 10, 2012 at 2:23
  • \$\begingroup\$ Is there a link to the executable en-route:) \$\endgroup\$
    – Drenai
    Commented Sep 27, 2012 at 16:30
  • \$\begingroup\$ @BrianBishop Sorry dude, I never did figure out what I was doing incorrectly with my image files in the resource file. Everything compiles, but the images never popup. \$\endgroup\$
    – anon
    Commented Oct 1, 2012 at 11:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.