//// Billiards. Table, with moving balls. //// Shoot the cueball. //// Swap velocities, when balls hit. String title="Shoot the cueball"; String how= "Click the rail to shoot", news=how; String subtitle= "(click the rail!)"; String author="Prof.BAM", NL="\n"; String filename="h4_billiards.pde/20412a"; // Assume size(600,400) // float left=50, top=50, right=550, bottom=350, bump=25; float center=(left+right)/2, middle=(top+bottom)/2; float spot= (left+center)/2, wide=right-left, high=bottom-top; float force=10; int score=0, diam=20, near=50; boolean debug=false; Ball red, yellow, blue; Ball cue= new Ball( 255,255,255, spot,middle ); Table t = new Table( left,top, right,bottom, bump ); void setup() { //// Setup: 600x400 (for 500x300 table). size(600,400); println( width, height ); //// Instantiate the balls. red= new Ball(color(255,0,0), 100,100); yellow= new Ball(color(255,255,0), 100,130); blue= new Ball(color(0,0,200), 100,160); resetCue(); reset(); wild(); } void resetCue() { cue.x=spot; cue.y=middle; cue.xx=cue.yy=0; } void reset() { //// Randomize the ball positions. red.reset(); yellow.reset(); blue.reset(); } void wild() { red.wild(); yellow.wild(); blue.wild(); } void draw() { //// Table, with billiard balls. scene(); //// Draw each ball. red.show(); yellow.show(); blue.show(); cue.show(); messages(); // action(); } void scene() { background( 255,220,220 ); // Pink floor. t.show(); } void action() { if (key == '?') { help(); return; } if (keyPressed) return; // Pause while key pressed. //// Move each ball, based on its velocity (dx,dy), //// and detect hits. cue.move(); red.move(); yellow.move(); blue.move(); //// Check for collisions. check( cue, red ); check( cue, yellow ); check( cue, blue ); check( red, yellow ); check( red, blue ); check( yellow, blue ); } void check( Ball p, Ball q) { float near=diam+2; //// Check for collisions; reverse both if hit. if (dist( p.x,p.y, q.x,q.y) < near) { score += 10; // Swap velocities. println( p.x,p.y, q.x,q.y, near ); float tmp; tmp = p.xx; p.xx= q.xx; q.xx= tmp; tmp = q.yy; q.yy= p.yy; p.yy= tmp; println( p.x,p.y, q.x,q.y ); //// Eliminate "ringing" (by moving in new direction. while( dist( p.x,p.y, q.x,q.y) < near) { println( dist( p.x,p.y, q.x,q.y), near ); p.move(); q.move(); } } } ///////////////// CLASSES class Table { float left,top, right,bottom, bump; Table( float left, float top, float right, float bottom, float bump ) { this.left=left; this.top=top; this.right=right; this.bottom=bottom; this.bump=bump; } void show() { //// lite-green light-table, with thick brown border, on cyan backgroud. rectMode(CORNERS); fill( 100,50,50 ); // Brown (wood) rail. rect( left-bump,top-bump, right+bump,bottom+bump ); fill( 100,200,100 ); // Light-green table. rect( left,top, right,bottom ); } boolean within( float x, float y ) { //// True iff x,y inside table. if (xright) return false; if (ybottom) return false; return true; } boolean bumper( float x, float y) { //// Chekc if outside thabnle but inside bumper. if (within(x,y)) return false; if (xright+bump) return false; if (ybottom+bump) return false; return true; } } class Ball { //// Billiard ball. color c; float x=0,y=0; // Position of this ball. float xx=0,yy=0; // Velocity (pixels per frame). float xxx=0.005, yyy=0.005; // Acceleration (friction). float diam=20; //// CONSTRUCTORS //// Ball( float x0, float y0 ) { //// 2-arg constructor sets position only. x= x0; y= y0; } Ball( color c0, float x0, float y0 ) { //// 3-arg constructor sets color & position. c= c0; x= x0; y= y0; } Ball( int r, int g, int b, float x0, float y0 ) { //// 5-arg constructor sets color & position. c= color(r,g,b); x= x0; y= y0; } Ball( ) { //// Default constructor must be defined, if any others are defined! } //// METHODS //// void move() { // Friction float trivial=0.5; if (abs(xx) right-diam/2) xx= -xx; if (y < top+diam/2) yy= -yy; if (y > bottom-diam/2) yy= -yy; } void show() { //// Draw at (x,y) fill(c); //-- text(c, x, y+30); ellipseMode(CENTER); ellipse(x,y, diam,diam); if (debug) report(); } void report() { text( x+","+y, x-10, y+20 ); text( xx+","+yy, x-10, y+30 ); } void reset() { // Randomly in right half. x= random( center, right-diam/2 ); y= random( top+diam/2, bottom-diam/2); xx=yy=0; } void wild() { //// Randomly modify the speeds. (Adds energy to the system.) xx += random(-5,+5); yy += random(-5,+5); xx *= random(0.5,2.5); yy *= random(0.5,2.5); } void shoot( float x2, float y2 ) { float run=x2-x, rise=y2-y, hyp=dist(x2,y2, x,y); xx = -force * run/hyp; // sine yy = -force * rise/hyp; // cosine } boolean hit( float x2, float y2 ) { println( "clicked: ", x,y+diam/2, dist(x2,y2, x,y+diam/2), 2*diam ); return dist(x2,y2, x,y+diam/2) < 2*diam; } boolean clicked() { return hit( mouseX, mouseY ); } }// class Ball // void messages() { fill(0); text( title, 30, 15); text( author+"/"+filename, 30, height-12 ); text( news, width/2, height-12 ); fill( 127,0,0 ); String hint= "Click rail to shoot; 'r' restarts; 'w' to go WILD, '?' for help."; text( hint, width/3, 15); fill(255,255,0); text( subtitle, 30, top-10); text( "force: "+force, width*3/4, top-10 ); fill( 0,255,255 ); text( "score: "+score, width*3/4, bottom+15 ); String s = "---"; if (debug) s="debug"; text( s, width/2, bottom+15 ); } void help() { float x=width/4, y=top+20; int next=0, h=12; fill( 150,0,150 ); text( "Click rail to shoot cueball.", x, y+h*next++ ); text( "Click any ball to reset it.", x, y+h*next++ ); text( "+/- to increase/decrease force.", x, y+h*next++ ); text( " 'w' wildly adds energy to the system!", x, y+h*next++ ); text( "KEYS:", x, y+h*next++ ); text( " 'r' to reset all; 'c' to set cueball", x, y+h*next++ ); text( " 's' to shoot; 'q' to quit, '?' for help", x, y+h*next++ ); text( " 'd' displays debugging information", x, y+h*next++ ); } //// EVENTS //// void keyPressed() { news= "keyPressed: "+key; if (key=='q') exit(); if (key=='r') reset(); if (key=='s') cue.shoot(mouseX,mouseY); if (key=='c') resetCue(); // if (key=='+') force *= 1.25; // Shoot harder. if (key=='=') force *= 1.25; if (key=='-') force *= 0.8; if (key=='w') { wild(); cue.wild(); } if (key=='d') debug = ! debug; } void mousePressed() { println( "mousePressed: ", mouseX, mouseY); news= "mousePressed: (" + mouseX+ "," +mouseY+ ")"; if (t.bumper( mouseX,mouseY)) cue.shoot( mouseX, mouseY); if (cue.clicked()) cue.reset(); if (red.clicked()) red.reset(); if (blue.clicked()) blue.reset(); if (yellow.clicked()) yellow.reset(); }