11
\$\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\$
  • 1
    \$\begingroup\$ possible duplicate of Recreate a 'Snake' game in a console/terminal \$\endgroup\$ – copy Sep 8 '12 at 18:23
  • 1
    \$\begingroup\$ @copy some people don't like to be restricted to terminals. \$\endgroup\$ – Griffin Sep 8 '12 at 18:29
  • \$\begingroup\$ does the 'snake cannot double back' rule apply if the snake is length=1? \$\endgroup\$ – Paul Prestidge Sep 10 '12 at 4:55
  • \$\begingroup\$ @chron, yes it does. At all times, snakes may only (truly) turn two ways, left and right. \$\endgroup\$ – mjgpy3 Sep 10 '12 at 11:09
2
\$\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.

| improve this answer | |
\$\endgroup\$
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.
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Great, I look forward to trying it! \$\endgroup\$ – mjgpy3 Sep 10 '12 at 2:23
  • \$\begingroup\$ Is there a link to the executable en-route:) \$\endgroup\$ – Drenai Sep 27 '12 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\$ – Rob Oct 1 '12 at 11:51
2
\$\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.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Very cool solution! \$\endgroup\$ – mjgpy3 Sep 11 '12 at 18:03
1
\$\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)
| improve this answer | |
\$\endgroup\$
  • \$\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 Sep 24 '12 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 Sep 24 '12 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\$ – KrystosTheOverlord Mar 21 '19 at 1:47
  • \$\begingroup\$ @KrystosTheOverlord hahah yes good point :D \$\endgroup\$ – stefreak Mar 22 '19 at 19:08
1
\$\begingroup\$

Javascript + Canvas: 436 characters (487 w/ HTML)

I've iterated over this design for a while now, and I'm not sure I can crunch it down much further without making some serious infrastructure or visual changes. For the sake of readability (ha), I've left newlines anywhere I'd otherwise need a semicolon anyway, making the character count the same as if I'd one-lined it.

The one criteria I wasn't able to fit in was "if the snake hits the wall, it dies"; for the sake of simplicity, the snake wraps to the opposite side of the field like in the original game. The score is also displayed constantly throughout the game instead of only on the kill screen, saving 3 characters.

You can play a hosted version here, move the snake with the arrow keys.

<canvas id=W width=640 height=640><script>C=W.getContext`2d`,W=20,F=32
R=_=>new Date%W**2,D=[0,-1,0,1]
O=(p,c,a=F)=>{C.fillStyle=c;C.fillRect(p%W*F,(p/W|0)*F,a,a)}
G=_=>{g=R(t=[]),h=g**7,m=n=3
window.onkeydown=e=>n=e.keyCode%4
L()};G(L=_=>{m%2!=n%2&&(m=n)
t.pop(t.unshift(h))
h=(h%W+D[m]+W)%W+((h/W|0)+D[3-m]+W)%W*W
h-g||t.push(g=R())
q=t.includes(h);setTimeout(q?G:L,q?3e3:67)
O(0,'#000',W*F)
for(s of[h,...t])O(s,q?'tan':'#080')
O(g,'red')
C.fillText('Score: '+t.length,F,F)})</script>

Here's the slightly-unminified version I've actually been editing:

<canvas id=W width=640 height=640><script>
C=W.getContext`2d`,W=20,F=32
R=_=>new Date%W**2,D=[0,-1,0,1]
O=(p,c,a=F)=>{C.fillStyle=c;C.fillRect(p%W*F,(p/W|0)*F,a,a)}
G=_=>{
  g=R(t=[]),h=g**7,m=n=3
  window.onkeydown=e=>n=e.keyCode%4
  L()
};G(L=_=>{
  m%2!=n%2&&(m=n)
  t.pop(t.unshift(h))
  h=(h%W+D[m]+W)%W+((h/W|0)+D[3-m]+W)%W*W
  h-g||t.push(g=R())
  q=t.includes(h);setTimeout(q?G:L,q?3e3:67)
  O(0,'#000',W*F)
  for(s of[h,...t])O(s,q?'tan':'#080')
  O(g,'red')
  C.fillText('Score: '+t.length,F,F)
})
</script>

Some of the clever tricks I've abused beyond the bog-standard JS golf tricks:

  • Giving an HTML element an id attribute exposes it in the global namespace with a matching name.
  • Applying a small modulus divisor to the current UNIX timestamp will give the appearance of a pseudo-random integer, so long as numbers aren't generated too soon after each other.
  • In the one case where I generate two "random" numbers too close together, I raise the latter to the power of 7 (chosen because it's a one-digit prime number) and re-modulus it to fit the desired range, making it much more difficult to tell that the two numbers are closely related.
  • Usage of CanvasContext2D.scale (C.scale) can be golfed by manually applying multiplication by a scale factor instead.
  • The keycodes of the arrow keys are adjacent, so I can extract them with a simple %4. This means any key will move the snake in a semi-unpredictable direction, but the arrow keys will work reliably.
  • Since those keycodes go in a rotational order, I was able to implement the check to prevent turning back on yourself with a simple check that the %2 of the old and new directions isn't equal.
  • Subtracting a modulo-wrapped number from its maximum value (the divisor minus 1) will map it to its equivalent number in the inverse sequence (so for n%4, 3-n will mean 0 -> 3, 1 -> 2, 2 -> 1, 3 -> 0). I use this to reverse the indexes of my input-to-offset map, allowing me to reuse it for both X and Y instead of needing separate maps for each.
  • A two-dimensional coordinate on a fixed-width grid can be stored in a single integer with the formula c = x+y*w. The individual coordinates can be retrieved with x = c%w and y = floor(c/w) respectively. This makes collision detection a trivial membership/equality test, and generating a random coordinate as simple as generating a random integer in a range of the grid's area.
  • Recursion through setTimeout will not bloat the stack, meaning stack overflow errors are a non-issue despite a recursive implementation.

And a small handful of tricks that are more specific to this particular exercise, as opposed to generally applicable:

  • I reuse the apple color for the score text color, to avoid an extra C.fillStyle='#FFF' that would be required for a unique coloration.
  • I reuse the same setTimeout call for both the kill screen duration and the game's tick rate, splitting my conditional to each argument instead.
  • The colors I've used are the shortest possible options - only 2 CSS colors have 3-character names (red and tan), and the other two colors I use are written as 4-character #FFF format colors.

Probably the most fundamental way this implementation could be golfed further would be to figure out a way to implement movement without splitting and recombining the head coordinates. The offset itself is easy - simply use W instead of 1 in the offset map. Wrapping, on the other hand, is the difficult part, and changing from that to collision with horizontal walls would simply be impossible without a lot of extra code due to the nature of this coordinate scheme.

| improve this answer | |
\$\endgroup\$
  • 2
    \$\begingroup\$ Welcome to the site! This looks promising, but as you note yourself you did not meet all requirements, so this answer is currently invalid as we do not allow partial answers. \$\endgroup\$ – Laikoni Jul 1 at 9:00
  • 2
    \$\begingroup\$ Please remove your answer until it is valid. \$\endgroup\$ – Jonathan Frech Jul 1 at 10:59
  • \$\begingroup\$ As the other comments have said, it is recommended you delete your answer as it doesn't meet all the requirements. However, you are able to edit your answers when deleted, and you can undelete them at any point, so I'd suggest deleting your answer for now, working on implementing the final requirement, then editing and undeleting once you've done so \$\endgroup\$ – caird coinheringaahing Jul 1 at 14:28
  • \$\begingroup\$ The proper procedure for handling invalid answers is to flag them as "in need of moderator intervention" after two days, not "not an answer" or "very low quality" \$\endgroup\$ – pppery Jul 2 at 2:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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