/* 3D version of the classic game "Snake" By Stewart Wilcox */ import java.applet.*; import java.awt.*; import java.awt.event.*; import java.lang.System.*; import java.util.Vector; //-------------------------------------------------------------------------------------main class public class snake extends Applet implements Runnable,KeyListener{ final int NC=6; //(half of the) number of circles on the border final float SZ=10; //(half of the) size of the playing field final int NR=8; //number of points used to draw the snake final int segtime=200; //time between segments of snake final float eps=0.001f; //small number (for intersecting lines with near clipping plane) final float radius=0.5f; //radius of snake int initlen; //initial length of snake final int extend=10; //number of segs to extend for each yummy int numdots; //number of yummy green dots at a time int wd,ht; Image backbuf; Graphics backg; frame pos; //affine transformation from worldspace to viewspace vec posvec; //position vector final vec zero=new vec(0,0,0); //duh vec[] pts; //points in a circle for drawing the snake Vector segs; //list of frames representing segments of snake Vector yum; //position of thing int longer=0; //number of segments that have to be added to snake boolean atemeself=false; int score; int level; long time; long lastseg; boolean showfps=false; boolean showvol=false; float fps=0; int frames=0; long lastfps; Thread t=null; boolean susp; AudioClip chomp; AudioClip lvlup; AudioClip die; boolean snd=true; int dotcol=0; Color[] cols={Color.red,Color.green,Color.blue}; final int numcols=3; boolean cam=false; boolean[] key; final int[] ctrls={KeyEvent.VK_LEFT,KeyEvent.VK_RIGHT,KeyEvent.VK_UP,KeyEvent.VK_DOWN,KeyEvent.VK_J,KeyEvent.VK_L,KeyEvent.VK_I,KeyEvent.VK_K}; int menu=1;// 0=playing 1=first start 2=died 3=paused private int param(String name,int def){ String s=getParameter(name); return (s==null)?def:Integer.parseInt(s); } public String getAppletInfo(){ return "3D snake game.\n" +"By Stewart Wilcox\n" +"Parameters:\n" +"width, height (getSize() sometimes fails in IE)\n" +"initlen = initial length of snake (default 60)\n" +"numdots = number of green dots present simultaneously (default 5)\n" +"sound_chomp = sound file played when green dot is eaten\n" +"sound_lvlup = sound file played when level increases\n" +"sound_die = sound played when player dies"; } public void init(){ wd=param("width",getSize().width); ht=param("height",getSize().height); initlen=param("initlen",60); numdots=param("numdots",5); backbuf=createImage(wd,ht); backg=backbuf.getGraphics(); pts=new vec[NR]; for(int i=0;i0) gameinit(menu<3); if(e.getKeyChar()=='p'||e.getKeyChar()=='P'){ if(menu==3) gameinit(false); else if(menu==0) menu=3; } if(e.getKeyChar()=='f'||e.getKeyChar()=='F'){ showfps=!showfps; if(showfps){lastfps=time;fps=0;frames=0;} } if(e.getKeyChar()=='v'||e.getKeyChar()=='V') showvol=!showvol; if(e.getKeyChar()=='d'||e.getKeyChar()=='D') dotcol=(dotcol+1)%numcols; if(e.getKeyChar()=='c'||e.getKeyChar()=='C') cam=!cam; if(e.getKeyChar()=='s'||e.getKeyChar()=='S') snd=!snd; e.consume(); if(menu>0) repaint(); } public void keyPressed(KeyEvent e){ for(int i=0;i<8;++i) if(e.getKeyCode()==ctrls[i]) key[i%4]=true; e.consume(); } public void keyReleased(KeyEvent e){ for(int i=0;i<8;++i) if(e.getKeyCode()==ctrls[i]) key[i%4]=false; e.consume(); } //-----------------------------------------------------------------------------------------------------Thread stuff public void start(){ if(t==null){ t=new Thread(this); t.start(); } } public void stop(){ t=null; } private vec rand(){ vec out=new vec((float)Math.random()*2-1,(float)Math.random()*2-1,(float)Math.random()*2-1); out.scale(SZ*0.9f); return out; } public void run(){ while(t!=null){ try{ if(menu>0){ synchronized(this){ while(menu>0) wait(); } } t.sleep(10); }catch(InterruptedException e){} long now=System.currentTimeMillis(); float diff=(float)(now-time); diff*=(level+4); pos.push(diff/2000); diff/=4000; if(key[0]^key[1]) pos.rot(0,key[0]?-diff:diff); if(key[2]^key[3]) pos.rot(1,key[2]?diff:-diff); frame inv=pos.inverse(); posvec=inv.apply(zero); if(posvec.x<-SZ||posvec.x>SZ||posvec.y<-SZ||posvec.y>SZ||posvec.z<-SZ||posvec.z>SZ) menu=2; if(atemeself) menu=2; for(int i=0;i=(8<0) --longer; else segs.removeElementAt(0); segs.addElement(inv); } if(showfps){ ++frames; if(now-lastfps>1000){ fps=(float)(frames)*1000/(now-lastfps); lastfps=now; frames=0; } } time=now; if(menu>0&&menu<3&&die!=null&&snd) die.play(); repaint(); } } //-------------------------------------------------------------------------------------------Graphics stuff private int scr(float x,float y){return (int)((x/y+1)*wd/2);} // private float fade(float y){if(menu>0) return 1/y/2; else return 1/y;} private void line(vec a,vec b,Graphics g,int col){ a=pos.apply(a); b=pos.apply(b); if(cam){++a.y;++b.y;} if(a.y<=1+eps&&b.y<=1+eps) return; if(a.y<1-eps||b.y<1-eps){ float t=(1-a.y)/(b.y-a.y); if(a.y<1) a.inter(b,1-t); else b.inter(a,t); } Color c; if(col==1) c=Color.red; else if(menu>0) c=Color.gray; else c=Color.black; g.setColor(c); // float z=fade((a.y+b.y)/2); // g.setColor((col==1)?new Color(1,1-z,1-z):new Color(1-z,1-z,1-z)); g.drawLine(scr(a.x,a.y),scr(a.z,a.y),scr(b.x,b.y),scr(b.z,b.y)); } private void disk(vec cen,Graphics g){ cen=pos.apply(cen); if(cam) ++cen.y; if(cen.y<1) return; // float z=fade(cen.y); // int r=(int)(z*40); // g.setColor(new Color(1-z,1,1-z)); int r=(int)(40f/cen.y); g.setColor(cols[dotcol]); g.fillOval(scr(cen.x,cen.y)-r,scr(cen.z,cen.y)-r,r*2+2,r*2+2); } public void paint(Graphics g){ g.setColor(Color.white); g.fillRect(0,0,wd,ht); //-----------------draw border // vec u=new vec(0,0,0),v=new vec(0,0,0); // for(int k=0;k<12;++k){ // for(int i=-NC;i<=NC;++i){ // for(int j=-NC;j<=NC;++j){ // switch(k){ // case 0:case 1:v=new vec(SZ*i/NC,SZ*j/NC,((k&1)==0)?SZ:-SZ);break; // case 2:case 3:v=new vec(SZ*j/NC,SZ*i/NC,((k&1)==0)?SZ:-SZ);break; // case 4:case 5:v=new vec(SZ*j/NC,((k&1)==0)?SZ:-SZ,SZ*i/NC);break; // case 6:case 7:v=new vec(SZ*i/NC,((k&1)==0)?SZ:-SZ,SZ*j/NC);break; // case 8:case 9:v=new vec(((k&1)==0)?SZ:-SZ,SZ*i/NC,SZ*j/NC);break; // case 10:case 11:v=new vec(((k&1)==0)?SZ:-SZ,SZ*j/NC,SZ*i/NC);break; // } // if(j>-NC) line(u,v,g,0); // u=v; // } // } // } vec u=zero,v=zero; for(int k=0;k<12;++k){ if(k<6) u=new vec(-SZ,-SZ,-SZ); else u=new vec(SZ,SZ,SZ); switch(k/2){ case 0:v=new vec(-SZ,-SZ,SZ);break; case 1:v=new vec(-SZ,SZ,-SZ);break; case 2:v=new vec(SZ,-SZ,-SZ);break; case 3:v=new vec(SZ,SZ,-SZ);break; case 4:v=new vec(SZ,-SZ,SZ);break; case 5:v=new vec(-SZ,SZ,SZ);break; } for(int i=-NC;i<=NC;++i){ switch(k%3){ case 0: u.x=v.x=SZ*i/NC;break; case 1: u.y=v.y=SZ*i/NC;break; case 2: u.z=v.z=SZ*i/NC;break; } line(u,v,g,0); } } //-----------------draw snake frame a,b; a=new frame(); for(int i=0;i0) line(b.apply(pts[j]),a.apply(pts[j]),g,1); } if(i>0&&i+30){ switch(menu){ case 1: g.drawString("Press spacebar to begin...",wd/2,ht/2);break; case 2: g.drawString("You died! Press spacebar to play again...",wd/2,ht/2);break; case 3: g.drawString("Paused. Press spacebar to continue...",wd/2,ht/2);break; } g.drawString("Collect the yummy dots!",20,ht-140); g.drawString("Use the arrow keys (or i.j.k.l) to manuever",20,ht-120); g.drawString("p = Pause",20,ht-100); g.drawString("d = cycle Dot colour",20,ht-80); g.drawString("c = toggle Camera position",20,ht-60); g.drawString("v = toggle Volume ratio display",20,ht-40); }else{ g.drawLine(wd/2+2,ht/2,wd/2+8,ht/2); g.drawLine(wd/2-2,ht/2,wd/2-8,ht/2); g.drawLine(wd/2,ht/2+2,wd/2,ht/2+8); g.drawLine(wd/2,ht/2-2,wd/2,ht/2-8); if(cam){ int rad=(int)(radius*wd/2); g.setColor(Color.red); g.drawOval(wd/2-rad,ht/2-rad,2*rad,2*rad); } } } public void update(Graphics g){ paint(backg); g.drawImage(backbuf,0,0,this); } } //-------------------------------------------------------------------------------------3d helper classes class vec{ float x; float y; float z; vec(float _x,float _y,float _z){x=_x;y=_y;z=_z;} vec(vec _v){this(_v.x,_v.y,_v.z);} void inter(vec b,float t){ //linear interpolation x=x*t+b.x*(1-t); y=y*t+b.y*(1-t); z=z*t+b.z*(1-t); } float distsq(vec b){ //squared distance from another vector return (x-b.x)*(x-b.x)+(y-b.y)*(y-b.y)+(z-b.z)*(z-b.z); } float distsqseg(vec b,vec c){ //squared distance from the line segment b-c float r=distsq(b),s=distsq(c),t=b.distsq(c); if(r>s+t) return s; //angle(this,c,b) is obtuse if(s>r+t) return r; //angle(this,b,c) is obtuse return (2*r*s+2*s*t+2*t*r-r*r-s*s-t*t)/(4*t); //Heron's area formula :D } void scale(float t){x*=t;y*=t;z*=t;} } class frame{ //represents the affine transformation v -> Rv+T private float[][] mat={{1,0,0,0},{0,1,0,0},{0,0,1,0}}; //mat=(R|T) frame(){} vec apply(vec in){ vec out=new vec(0,0,0); out.x=mat[0][0]*in.x+mat[0][1]*in.y+mat[0][2]*in.z+mat[0][3]; out.y=mat[1][0]*in.x+mat[1][1]*in.y+mat[1][2]*in.z+mat[1][3]; out.z=mat[2][0]*in.x+mat[2][1]*in.y+mat[2][2]*in.z+mat[2][3]; return out; } void rot(int i,double rad){ //i=0 or 1 for rotation about z and x respectively float c=(float)Math.cos(rad),s=(float)Math.sin(rad); for(int j=0;j<4;++j){ float tmp=mat[i][j]; mat[i][j]=tmp*c-mat[i+1][j]*s; mat[i+1][j]=tmp*s+mat[i+1][j]*c; } } void push(float dist){ mat[1][3]-=dist; } frame inverse(){//assume this is an orthogonal transformation, so det=1 frame out=new frame(); for(int i=0;i<3;++i) for(int j=0;j<3;++j) out.mat[j][i]=mat[(i+1)%3][(j+1)%3]*mat[(i+2)%3][(j+2)%3]-mat[(i+2)%3][(j+1)%3]*mat[(i+1)%3][(j+2)%3]; vec tra=out.apply(new vec(-mat[0][3],-mat[1][3],-mat[2][3])); out.mat[0][3]=tra.x; out.mat[1][3]=tra.y; out.mat[2][3]=tra.z; return out; } }