import java.awt.*; import java.awt.image.*; import java.applet.*; import java.awt.event.*; import java.io.*; import java.net.*; import sun.audio.*; public class cluster extends Applet implements Runnable, MouseListener, MouseMotionListener{ int i,j,k,l,m,n,x,y; int lastX,lastY,gameMode=0,ballnum,tempball,ballMidX,ballMidY,ballR; int dragOffsX,dragOffsY,busycount,playing=-1; int ip,jp,kp,lp, is,js,ks,fronts,backs,id,jd,kd,ipt,idc,mi,mj,mk,wi,dragging=0; boolean busy=false,drawing; double ap,bp,cp,as,an,amd, adc,bdc,cdc, apt,bpt,cpt,dpt, dragScale=0.012; double cornX[]=new double[8],cornY[]=new double[8],cornZ[]=new double[8]; double projX[]=new double[8],projY[]=new double[8],projZ[]=new double[8]; double pntX[]=new double[32],pntY[]=new double[32],pntZ[]=new double[32]; double scrX[]=new double[32],scrY[]=new double[32],scrZ[]=new double[32]; double frontZ[]=new double[36],backZ[]=new double[36]; final double xa[]={1,0,0},ya[]={0,1,0},za[]={0,0,1}; double xaxis[]=new double[3],yaxis[]=new double[3],zaxis[]=new double[3]; double ut[]=new double[3],vt[]=new double[3],wt[]=new double[3]; double frontDir[]=new double[7]; double boxpos[]=new double[81]; double ballX[]=new double[27],ballY[]=new double[27],ballZ[]=new double[27]; final double scale=11.0,ballsize=0.341; long lastClick,nextTime; int faces[]={6,4,2, 7,3,5, 0,4,1, 6,2,7, 2,0,3, 6,7,4}; int bbx[]={0,28,58,90,124,160,198,90,46,0, 210,180,148,114,78,40,0,132,174,218}; int bby[]={34,34,34,34,34,34,34,70,67,64, 120,118,118,114,113,111,110,72,74,74}; int linefaces[]=new int[72],cubedata[]=new int [27],balltype[]=new int [27]; int backOrder[]=new int[36],frontOrder[]=new int[36],ballOrder[]=new int [27]; int lFrom[]=new int[36],lTo[]=new int[36]; Image b1,b2,background,bballs[]=new Image[20]; Graphics bG,bsG; ImageProducer improd; MediaTracker mt; Color gCol[]=new Color[36]; InputStream sounds[]=new AudioDataStream[2]; byte sndData[][]=new byte[2][]; right rightApplet; bottom bottomApplet; Thread runner; public void init() { mt=new MediaTracker(this); InputStream imgstrm,sndstrm; byte headerBuf[]=new byte[28]; for (i=0;i<2;i++) { sndstrm = getClass().getResourceAsStream("sound"+i+".au"); try { j=sndstrm.available()-28; sndData[i] = new byte[j]; k=0; while (k<28) k+=sndstrm.read(headerBuf,k,28-k); sndstrm.read(sndData[i]); sounds[i]=new AudioDataStream(new AudioData(sndData[i])); sounds[i].mark(j); } catch (IOException ioe) {} } imgstrm = getClass().getResourceAsStream("balls1.gif"); try { byte imageBytes1[] = new byte[imgstrm.available()]; imgstrm.read(imageBytes1); b1 = Toolkit.getDefaultToolkit().createImage(imageBytes1); } catch (IOException ioe) {b1=createImage(1,1);} mt.addImage(b1,0); imgstrm = getClass().getResourceAsStream("balls2.gif"); try { byte imageBytes2[] = new byte[imgstrm.available()]; imgstrm.read(imageBytes2); b2 = Toolkit.getDefaultToolkit().createImage(imageBytes2); } catch (IOException ioe) {b2=createImage(1,1);} mt.addImage(b2,0); try { mt.waitForID(0); } catch(InterruptedException e) {} improd=b1.getSource(); j=0;k=30; for (i=0;i<10;i++) { bballs[i]=createImage(new FilteredImageSource(improd, new CropImageFilter(j,0,k,k))); mt.addImage(bballs[i],1); j+=k; k+=2; } improd=b2.getSource(); j=0;k=30; for (i=0;i<10;i++) { bballs[i+10]=createImage(new FilteredImageSource(improd, new CropImageFilter(j,0,k,k))); mt.addImage(bballs[i+10],1); j+=k; k+=2; } try { mt.waitForID(1); } catch(InterruptedException e) {} frontDir[6]=-1; background=createImage(190,190); bG=background.getGraphics(); buildCoordinates(); buildGrays(); reset(); addMouseListener(this); addMouseMotionListener(this); } public void reset() { int i; for (i=0;i<3;i++) { xaxis[i]=xa[i]; // Reset co-ordinate system yaxis[i]=ya[i]; zaxis[i]=za[i]; } busy=false; for (i=0;i<27;i++) // Clear cube cubedata[i]=0; while (rightApplet==null || bottomApplet==null) { rightApplet=(right)getAppletContext().getApplet("right"); bottomApplet=(bottom)getAppletContext().getApplet("bottom"); } tempball=-1; rightApplet.clearBoard(); bottomApplet.setNewMessage(0); drawing=false; performDrawing(); } public void buildCoordinates() { // Compute absolute locations of all corners and points of interest for (i=0;i<2;i++) for (j=0;j<2;j++) for (k=0;k<2;k++) { cornX[i*4+j*2+k]=1.0-2.0*(k&1); cornY[i*4+j*2+k]=1.0-2.0*(j&1); cornZ[i*4+j*2+k]=1.0-2.0*(i&1); } m=0; for (i=0;i<8;i++) { pntX[i*4]=cornX[i]; pntY[i*4]=cornY[i]; pntZ[i*4]=cornZ[i]; for (j=0;j<3;j++) if ((i&(1<0.99) linefaces[l++]=0; else if (pntX[j]<-0.99) linefaces[l++]=1; if (Math.abs(pntY[j]-pntY[k])<0.01) if (pntY[j]>0.99) linefaces[l++]=2; else if (pntY[j]<-0.99) linefaces[l++]=3; if (Math.abs(pntZ[j]-pntZ[k])<0.01) if (pntZ[j]>0.99) linefaces[l++]=4; else if (pntZ[j]<-0.99) linefaces[l++]=5; } for (i=0;i<3;i++) // Find co-ordinates of marble midpoints for (j=0;j<3;j++) for (k=0;k<3;k++) { boxpos[i*27+j*9+k*3]=0.66667*(1-k); boxpos[i*27+j*9+k*3+1]=0.66667*(1-j); boxpos[i*27+j*9+k*3+2]=0.66667*(1-i); } } public void project() { // Compute screen locations of all points of interest, given the viewing angle // and system for (ip=0;ip<32;ip++) { scrZ[ip]=scalProd(pntX[ip],pntY[ip],pntZ[ip],zaxis[0],zaxis[1],zaxis[2]); ap=(5.0+scrZ[ip])*scale; bp=scalProd(pntX[ip],pntY[ip],pntZ[ip],xaxis[0],xaxis[1],xaxis[2]); cp=scalProd(pntX[ip],pntY[ip],pntZ[ip],yaxis[0],yaxis[1],yaxis[2]); scrX[ip]=95.0+ap*bp; scrY[ip]=95.0+ap*cp; if ((ip&3)==0) { jp=ip>>2; projX[jp]=bp*ap/50.0; projY[jp]=cp*ap/50.0; projZ[jp]=scrZ[ip]; } } for (ip=0;ip<6;ip++) // Compute normals { jp=faces[ip*3];kp=faces[ip*3+1];lp=faces[ip*3+2]; ut[0]=projX[kp]-projX[jp]; ut[1]=projY[kp]-projY[jp]; ut[2]=projZ[kp]-projZ[jp]; vt[0]=projX[lp]-projX[jp]; vt[1]=projY[lp]-projY[jp]; vt[2]=projZ[lp]-projZ[jp]; crossProd(ut,vt,wt); frontDir[ip]=wt[2]; } ballnum=0; for (ip=0;ip<27;ip++) if (cubedata[ip]>0) { balltype[ballnum]=cubedata[ip]; ballOrder[ballnum]=ballnum; ballZ[ballnum]=scale*(5.0+scalProd(boxpos[ip*3],boxpos[ip*3+1],boxpos[ip*3+2], zaxis[0],zaxis[1],zaxis[2])); ballX[ballnum]=95.0+ballZ[ballnum]*scalProd(boxpos[ip*3],boxpos[ip*3+1],boxpos[ip*3+2], xaxis[0],xaxis[1],xaxis[2]); ballY[ballnum]=95.0+ballZ[ballnum++]*scalProd(boxpos[ip*3],boxpos[ip*3+1],boxpos[ip*3+2], yaxis[0],yaxis[1],yaxis[2]); } } public void sortStuff() { fronts=0;backs=0; for (is=0;is<36;is++) // Check whether front line or back line { if (frontDir[linefaces[is*2]]>0 || frontDir[linefaces[is*2+1]]>0) { frontZ[fronts]=scrZ[lFrom[is]]+scrZ[lTo[is]]; frontOrder[fronts++]=is; } else { backZ[backs]=scrZ[lFrom[is]]+scrZ[lTo[is]]; backOrder[backs++]=is; } } for (is=0;isfrontZ[js]) { ks=frontOrder[is]; frontOrder[is]=frontOrder[js]; frontOrder[js]=ks; as=frontZ[is]; frontZ[is]=frontZ[js]; frontZ[js]=as; } } for (is=0;isbackZ[js]) { ks=backOrder[is]; backOrder[is]=backOrder[js]; backOrder[js]=ks; as=backZ[is]; backZ[is]=backZ[js]; backZ[js]=as; } } for (is=0;isballZ[js]) { ks=ballOrder[is]; ballOrder[is]=ballOrder[js]; ballOrder[js]=ks; as=ballZ[is]; ballZ[is]=ballZ[js]; ballZ[js]=as; } } } public void drawCube() { int id,jd=0,idc=0; bG.setColor(Color.black); bG.fillRect(0,0,190,190); // First draw all the lines on faces away from the viewer for (id=0;id=0) cubedata[tempball]=0; tempball=-1; dpt=10000.0; for (ipt=0;ipt<27;ipt++) if (cubedata[ipt]==0) { apt=scale*(5.0+scalProd(boxpos[ipt*3],boxpos[ipt*3+1],boxpos[ipt*3+2], zaxis[0],zaxis[1],zaxis[2])); bpt=95.0+apt*scalProd(boxpos[ipt*3],boxpos[ipt*3+1],boxpos[ipt*3+2], xaxis[0],xaxis[1],xaxis[2]); cpt=95.0+apt*scalProd(boxpos[ipt*3],boxpos[ipt*3+1],boxpos[ipt*3+2], yaxis[0],yaxis[1],yaxis[2]); apt*=ballsize; bpt=(bpt-x)*(bpt-x); cpt=(cpt-y)*(cpt-y); if ((bpt+cpt=0) cubedata[tempball]=3; } public void performDrawing() { if (!drawing) { drawing=true; project(); sortStuff(); drawCube(); drawing=false; } } public void placeComputerBall(int b) // Called by the AI routine when it has finished thinking { cubedata[b]=12; performDrawing(); repaint(); busycount=10; } public void computerWins(long l) { long tl=l; for (wi=0;wi<27;wi++) { if ((tl&3)>0) cubedata[wi]=12; tl=tl>>>2; } bottomApplet.setNewMessage(3); } public void playerWins(long l) { long tl=l; for (wi=0;wi<27;wi++) { if ((tl&3)>0) cubedata[wi]=10; tl=tl>>>2; } bottomApplet.setNewMessage(2); } public void buildGrays() // Compute gray levels for lines and marbles { for (i=0;i<36;i++) gCol[i]=new Color(45+6*i,45+6*i,45+6*i); } public void start() { if (runner==null) { runner=new Thread(this); runner.start(); } } public void stop() { if ((runner!=null)&&(runner.isAlive())) { runner.stop(); } runner=null; } public void run() { int i,j; while (runner!=null) { try { runner.sleep(Math.max(25,nextTime-System.currentTimeMillis())); } catch (InterruptedException e) {} nextTime=System.currentTimeMillis()+120; if (busy) // Player not allowed to move. Blink all blinkable marbles { j=0; for (i=0;i<27;i++) if ((cubedata[i]&8)>0) { cubedata[i]^=1; j=cubedata[i]; } if (busycount>0) { busycount--; if (busycount==0) { for (i=0;i<27;i++) cubedata[i]&=6; busy=false; bottomApplet.setNewMessage(0); } else if (busycount==5) playSound(1); } if (j>0) { performDrawing(); repaint(); } } } } public void mouseDragged(MouseEvent me) { x=me.getX(); y=me.getY(); if (dragging==1) // Rotating grid. Determine new viewing angle and system { amd=(lastX-x)*dragScale; zaxis[0]+=amd*xaxis[0]; zaxis[1]+=amd*xaxis[1]; zaxis[2]+=amd*xaxis[2]; normalize(zaxis); crossProd(yaxis,zaxis,xaxis); amd=(lastY-y)*dragScale; zaxis[0]+=amd*yaxis[0]; zaxis[1]+=amd*yaxis[1]; zaxis[2]+=amd*yaxis[2]; normalize(zaxis); crossProd(zaxis,xaxis,yaxis); lastX=x; lastY=y; performDrawing(); repaint(); } else if (dragging==2) // Dragging marble. { placeTemp(x-dragOffsX,y-dragOffsY); performDrawing(); repaint(); } } public void mouseMoved(MouseEvent me){} public void mouseEntered(MouseEvent me){} public void mouseExited(MouseEvent me){} public void mouseReleased(MouseEvent me) { dragging=0; } public void mouseClicked(MouseEvent me) { if (System.currentTimeMillis()-lastClick<250 && tempball>=0) // Double-clicked? { cubedata[tempball]=2; mi=tempball; tempball=-1; performDrawing(); repaint(); busycount=-1; busy=true; mk=0; for (mj=0;mj<27;mj++) if (cubedata[mj]>0) mk++; if (mk<27) { bottomApplet.setNewMessage(1); playSound(0); rightApplet.registerMove(mi); // Let the AI routine know it's the computer's turn } else bottomApplet.setNewMessage(4); // Tied game } else lastClick=System.currentTimeMillis(); } public void mousePressed(MouseEvent me) { x=me.getX(); y=me.getY(); lastX=x; lastY=y; if (!busy) { if (tempball<0) { placeTemp(x,y); // Try to put a temporary marble where the cursor is if (tempball<0) // No marble placed, must be outside. Prepare grid rotation dragging=1; else { dragOffsX=0; dragOffsY=0; dragging=2; performDrawing(); repaint(); } } else { if ((x-ballMidX)*(x-ballMidX)+(y-ballMidY)*(y-ballMidY)=0) AudioPlayer.player.stop(sounds[playing]); try{ sounds[snd].reset(); } catch(IOException ioe){} AudioPlayer.player.start(sounds[snd]); playing=snd; } public void update(Graphics g) { g.drawImage(background,0,0,this); } public void paint(Graphics g) { g.drawImage(background,0,0,this); } }