BANK-IT-BALL

In 2024 I took a digital art course on creative coding that applied object-oriented programming fundamentals to expressive interactive experiences such as animations, interfaces, and games. This was an opportunity to apply my expanding programming skills to computation concepts based on interactive design, media art, and game design. The creation of original interactive projects was done using the graphical Processing IDE that utilizes Java and a GUI to provide both compilation and execution.

 

The Bank-it-Ball game was my final course project and it is styled as an interactive basketball shoot that might be found in an arcade. The player has six shots to throw the ball and score by having it fall through the basket. The twist in Bank-it-Ball is that there are two vertical obstacles that block the basket and so the ball must be banked and bounced off those obstacles. Eash play through the walls will change position to keep the challenge fresh and make the difficulty variable. The Microsoft Designer Image Creator application was the primary AI image generation tool used for this project and sound effects were done using Audiogen.

 

The Bank-It-Ball game is published on the openprocessing.org website as a p5.js JavaScript translated version of the original Processing IDE Java game. The game must be run within a PC desktop web browser using a keyboard & mouse to throw the basketballs. There is no mobile touch interface support currently. Launching the ball with the mouse is more finesse than speed, but be sure to release a throw attempt while the ball is still within the throw zone. I was able to utilize the hilite.me website to help generate HTML formatted snippets of the original Java code that I then manually embedded as custom HTML within my webpage here. The translated JavaScript is also provided below.

Game published and hosted on the openprocessing.org website.

 

PC keyboard & mouse support only at this time.

 

GAME PLAY VIDEO

Press the play button below to start the video. 

DEVELOPMENT

Developing this game was a great way to apply object-oriented programming (OOP) skills that I acquired in my other Java language courses. It was also rewarding to implement graphical animation elements that make for a visually intriguing game. In the context of creative coding, the Bank-it-Ball game might also be considered a prototype to a more advanced or refined version of a future arcade game production. The Processing IDE's ability to code graphical shapes, apply images, and insert sounds lends itself well to the creative coding domain.

 

Some basic physics-like elements were coded into the final game to have the basketball behave like its real-world counterpart. Wall bounces were designed to measure the distance of the ball's center to the wall edge each update frame and then reverse the direction, an equal and opposite reaction, when the distances were about a ball radius or less in tolerance. Air resistance was also applied to slow the ball slightly as it travels. Likewise, an artificial gravity acceleration was applied where a small, but increasing, velocity downwards is added each update frame. To keep the action going, if the ball loses bounce height that is below the basketball rim, then the next shot is set up and the player doesn't have to wait for the ball to dribble-out on the floor.

BANK-IT-BALL PDE JAVA SOURCE CODE

Press [SHIFT]+[Mousewheel] to scroll left and right.

BankItBall.pde

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
// BankItBall
// © 2024 Brenton Vavrek

import java.util.ArrayList;
import processing.sound.*;  // Sketch > Import Library > Manage Libraries > Sound (Install)

//screen variables
int screenWidth = 1280;
int screenHeight = 720;
int topHeight = 100;

//board variables
int wallX = 0;
int wallY = 360;
int wallHeight = 29;
int wallWidth = 600;
int bottomPos = screenHeight - 3 * wallHeight;
int ceilingHeight = bottomPos - topHeight;
int launchAreaWidth = (int) (ceilingHeight * 0.6);
int launchAreaHeight = (int) (ceilingHeight * 0.6);
int ballRadius = 26;
float mouseSpeedX = 0;
float mouseSpeedY = 0;
int basketHeight = topHeight + (int) (ceilingHeight * 0.4);
int basketRimX = (int) (wallX + screenWidth * 0.90);
int basketWidth = screenWidth - basketRimX;

//font variable
PFont GameFont;

//player variables
int activePlayer = 1;
int playerOneR = 204;
int playerOneG = 85;
int playerOneB = 0;

//score block variables
int basketZoneHeight = 20;
int basketZoneWidth = basketWidth;
int basketZoneAX = basketRimX;
int basketZoneAY = basketHeight;
int basketZoneBX = basketRimX;
int basketZoneBY = basketHeight + basketZoneHeight;
int basketZoneR = 0;
int basketZoneG = 255;
int basketZoneB = 0;

//score board variables
int scoreBoardWidth = screenWidth/4;
int scoreBoardHeight = topHeight/2;
int scoreBoardX = screenWidth/2-scoreBoardWidth/2;
int scoreBoardY = topHeight/4;
int updatedPlayerOneScore = 0;
boolean playerOneBasket = false;

//new game button variables
int buttonX = screenWidth/5;
int buttonY = topHeight/4;
int buttonWidth = screenWidth/8;
int buttonHeight = topHeight/2;

// general game variables
boolean gameOver = false;
boolean ballInPlay = false;
int ballMaxCount = 6;
int throwCount = 0;
int frameCounter = 0;

// mouse variables
boolean mouseHold = false;

// file media variables
PImage img, img2;
SoundFile sndfilebounce;
SoundFile sndfilewhoosh;
SoundFile sndfilebuzzer;
SoundFile sndfilewhistle;

boolean buzzerPlayed = false;

// screen setup
// open files once here so performance not impacted
void setup(){
  size(1280, 720, P3D);
  background(255);
  GameFont = createFont("Arial Bold",24);
  // AI image created using Image Creator from Microsoft Designer
  img = loadImage("images/gym.jpg");
  // AI image created using Image Creator from Microsoft Designer
  img2 = loadImage("images/basketballnet2.png");
  // royalty-free mp3 file from pixabay.com sound effect library
  // cropped and enhanced using an editor
  sndfilebounce = new SoundFile(this, "sounds/ball-bounce.mp3");
  // royalty-free mp3 file from pixabay.com sound effect library
  // cropped and enhanced using an editor
  sndfilewhoosh = new SoundFile(this, "sounds/whoosh.mp3");
  // royalty-free mp3 file from pixabay.com sound effect library
  // cropped and enhanced using an editor
  sndfilebuzzer = new SoundFile(this, "sounds/buzzer.mp3");
  // royalty-free mp3 file from pixabay.com sound effect library
  // cropped and enhanced using an editor
  sndfilewhistle = new SoundFile(this, "sounds/whistle.mp3");
  // start game with a whistle
  sndfilewhistle.play();
}

// array list for the balls
ArrayList<Ball> balls = new ArrayList<Ball>();

// obstacle wall variables
int obstacleOneStartX = (int) (wallX + screenWidth * 0.6);
int obstacleTwoStartX = (int) (wallX + screenWidth * 0.4);
int obstacleWiggleX = (int) (wallX + screenWidth * 0.05);

// list to create the barrier walls of the board
Wall[] walls = {
  // roof
  new Wall(wallX, topHeight, wallHeight, screenWidth),
  // floor
  new Wall(wallX, bottomPos, wallHeight, screenWidth),
  // obstacle 1
  new Wall(obstacleOneStartX, topHeight, (int) (ceilingHeight * 0.6 + wallHeight/2) , wallHeight),
  // obstacle 2
  new Wall(obstacleTwoStartX, topHeight+ (int) (ceilingHeight * 0.4), (int) (ceilingHeight * 0.6 + wallHeight/2) , wallHeight),
  // rim
  new Wall(basketRimX, basketHeight, wallHeight * 2, wallHeight)
};

// list for score zones
// for baskets there are two zones stacked ontop each other
// a basket is when the top zone and bottom zone are tagged in sequence
BasketZone[] zones = {
  new BasketZone(basketZoneAX, basketZoneAY, basketZoneWidth, basketZoneHeight, 
  basketZoneBX, basketZoneBY, basketZoneWidth, basketZoneHeight, 1, basketZoneR, basketZoneG, basketZoneB),
};

// scoreboard
ScoreBoard scoreboard = new ScoreBoard(scoreBoardX, scoreBoardY, scoreBoardWidth, scoreBoardHeight);

// draw out game and update states
void draw(){
  // basic setup for each iteration
  mouseSpeedX = (mouseX - pmouseX);
  mouseSpeedY = (mouseY - pmouseY);
  background(220);
  noFill();

  // reset fill and stroke styles to default
  resetStyle();
  
  // draw game name
  stroke(10);
  textAlign(CENTER,CENTER);
  fill(204, 85, 0);
  text("Bank-it-Ball", buttonX/2, buttonY + (buttonHeight/2));
  fill(0);
  text("Bank-it-Ball", buttonX/2 + 2, buttonY + (buttonHeight/2) + 2);
  
  // reset fill and stroke styles to default
  resetStyle();
  
  // draw gym background
  fill(255,242,229);
  rect(wallX, topHeight, screenWidth, ceilingHeight);
  
  noStroke();
  textureMode(NORMAL); 
  beginShape();
  texture(img);
  vertex(wallX, topHeight, 0, 0);
  vertex(screenWidth, topHeight, 1, 0);
  vertex(screenWidth, bottomPos, 1, 1);
  vertex(wallX, bottomPos, 0, 1);
  endShape();

  // reset fill and stroke styles to default
  resetStyle();

  // draw launch area.
  fill(255,229,204,100);
  stroke(0,0,0,0);
  rect(wallX, bottomPos - launchAreaHeight, launchAreaWidth, launchAreaHeight);
  textAlign(CENTER,CENTER);
  fill(255);
  text("THROW ZONE (CLICK)", launchAreaWidth/2, bottomPos - launchAreaHeight/2);

  // reset fill and stroke styles to default
  resetStyle();
  
  // draw backboard
  fill(205);
  int backboardWidth = 5;
  int backboardHeight = 150;
  rect(screenWidth - backboardWidth, basketHeight - backboardHeight, backboardWidth, backboardHeight);
  
  // draw new game button and check for hover
  if (mouseX > buttonX && mouseX < buttonX + buttonWidth && mouseY > buttonY && mouseY < buttonY + buttonHeight) {
    fill(240);
  } else {
    fill(205);
  }
  strokeWeight(5);
  rect(buttonX, buttonY, buttonWidth, buttonHeight);
  strokeWeight(1);
  fill(1);
  textAlign(CENTER,CENTER);
  textFont(GameFont);
  text("New Game", buttonX + (buttonWidth/2), buttonY + (buttonHeight/2));
  
  // reset fill and stroke styles to default
  resetStyle();
  
  // display the scorezones
  // this is for development and debug purposes only so that the zones can be viewed
  // for interim game finalization, this is commented out and retained only for future development ideas
  //for (BasketZone s : zones){
  // s.display();
  //}
  
  // check for the ball count
  if (throwCount + 1 > ballMaxCount && mouseHold == false){
    gameOver = true;
  }

  // display walls except virtual rim wall
  for (int i = 0; i < walls.length - 1; i++) {
    // the last wall is virtual rim, make it tranparent.
    walls[i].display();
  }
  
  // display virtual rim wall as transparent.
  walls[(walls.length - 1)].displaytransparent(); // this is the final mode with rim wall transparent
  //walls[(walls.length - 1)].display();  // this is for development and debug purposes only so rim can be viewed
  
  // reset fill and stroke styles to default
  resetStyle();
  
  // display the scoreboard
  scoreboard.display();
  
  // ball physics
  for (Ball b : balls) {
    b.applyGravity(bottomPos);
    b.applyAirFriction();
    
    b.checkBoundaryCollision(sndfilebounce);
    for (Wall w : walls) {
      b.checkWallCollision(w, sndfilebounce);
    }

    b.update();
    b.display();
  }
  
  // check for mouse button holding and set the balls to the location of the mouse for throw and release
  if (mouseHold && !gameOver && !ballInPlay) {
      if ((mouseX > wallX + ballRadius) && (mouseX < launchAreaWidth - ballRadius) && (mouseY < bottomPos - ballRadius) && (mouseY > launchAreaHeight + ballRadius)){
          balls.set(balls.size() - 1, new Ball(mouseX, mouseY, ballRadius, activePlayer, playerOneR, playerOneG, playerOneB));
    }
  }
  
  // update scoreboard
  int playerOneTurnScore = 0;
  for (Ball b : balls){
    for (BasketZone z : zones){
        if (!playerOneBasket){
            playerOneTurnScore = z.checkBasketZone(b, sndfilewhoosh);
        }
        if (playerOneTurnScore == 1) {
           playerOneBasket = true;
        }
    }
  }
  
  // update scores and scoreboard display
  updatedPlayerOneScore += playerOneTurnScore;
  scoreboard.setPlayerScore(updatedPlayerOneScore);
  scoreboard.display();
  
  // verify no balls in motion prior to new throws or info updates
  for (Ball b : balls) {
     if ((b.velocity.x != 0) || (b.velocity.y != 0)) {
        ballInPlay = true;
        break;
     } else {
        ballInPlay = false;
     }
  }
  
  // wait until the ball is no longer in play to end game or update player turn status
  if (!ballInPlay){
      if (gameOver) {
        if (buzzerPlayed == false){
          sndfilebuzzer.play();
          buzzerPlayed = true;
        }
        if (scoreboard.getPlayerScore() > scoreboard.getHighScore()){
            fill(255, 0, 0);
            text("GAME OVER  |  New High Score", (scoreBoardX + scoreBoardWidth) + (screenWidth - (scoreBoardX + scoreBoardWidth))/2, buttonY + (buttonHeight/2));
          } else {
            fill(135, 31, 120);
            text("GAME OVER", (scoreBoardX + scoreBoardWidth) + (screenWidth - (scoreBoardX + scoreBoardWidth))/2, buttonY + (buttonHeight/2));
          }
      } else {
          fill(playerOneR,playerOneG,playerOneB);
          text("PLAYER " + "  |  Shot " + (throwCount + 1) + " of " + ballMaxCount, (scoreBoardX + scoreBoardWidth) + (screenWidth - (scoreBoardX + scoreBoardWidth))/2, buttonY + (buttonHeight/2));
      }
  }
  
  // delete ball if just dead bouncing below rim level such that score not possible
  // or in event of a processing glitch where update teleports outside play zone
  boolean deleteBall = false;
  for (Ball b : balls) {
     if ((!mouseHold && ballInPlay && (b.velocity.mag() < 10) && (b.position.y > bottomPos - ceilingHeight * 0.25 )) || 
          (b.position.y > bottomPos) || (b.position.y < topHeight)) {
        deleteBall = true;
     } 
  }
  
  // delete ball based on determinations
  if (deleteBall){
    ballInPlay = false;
    balls.remove(0);
  }

  // draw basket netting
  fill(255,255,255,1);
  noStroke();
  textureMode(NORMAL); 
  beginShape();
  texture(img2);
  vertex(basketRimX, basketHeight, 0, 0);
  vertex(basketRimX + basketWidth, basketHeight, 1, 0);
  vertex(basketRimX + basketWidth, basketHeight + basketWidth * 0.75, 1, 1);
  vertex(basketRimX, basketHeight + basketWidth * 0.75, 0, 1);
  endShape();

  // reset fill and stroke styles to default
  resetStyle();
}

void resetStyle() {
  fill(0);
  stroke(0);
  strokeWeight(1);
}

void mousePressed(){
  //spawn the ball at the location of the player's mouse, alternate between player colors
  if (!ballInPlay && mousePressed && (mouseX > wallX + ballRadius) && (mouseX < launchAreaWidth + ballRadius) && (mouseY < bottomPos - ballRadius) && (mouseY > bottomPos - launchAreaHeight + ballRadius) && gameOver == false){
     // clear balls laying around
     if (balls.size() > 0) {
       balls.remove(balls.size() - 1);
     }
     if (mouseHold == false) {
         balls.add(new Ball(mouseX, mouseY, ballRadius, activePlayer, playerOneR, playerOneG, playerOneB));
     }
     
    for (BasketZone z : zones){
      z.resetBasketZone();
    }
    
    playerOneBasket = false;
     
     mouseHold = true;
  }
  
  // clear the game board of balls when new game button is pressed and reset game variables
  if (mouseX > buttonX && mouseX < buttonX + buttonWidth && mouseY > buttonY && mouseY < buttonY + buttonHeight){
    balls.clear();
    gameOver = false;
    throwCount = 0;
    activePlayer = 1;
    ballInPlay = false;
    updatedPlayerOneScore = 0;
    playerOneBasket = false;
    for (BasketZone z : zones){
      z.resetBasketZone();
      
    // randomize the obstacle walls
    walls[2].wallX = (int) random(obstacleOneStartX - obstacleWiggleX, obstacleOneStartX + obstacleWiggleX);
    walls[3].wallX = (int) random(obstacleTwoStartX - obstacleWiggleX, obstacleTwoStartX + obstacleWiggleX);  
    }
    
    buzzerPlayed = false;
    sndfilewhistle.play();
  }
    
}

void mouseReleased(){
  // initialize ball position at the mouse
  int ballXpos = mouseX;
  int ballYpos = mouseY;
  
  // launch velocities and game settings at throw moment
  if (mouseHold == true && gameOver == false) {
    mouseHold = false; 
    
    //checking for mouse speeds to keep the balls movement consistent
    if (((mouseSpeedX == 0) && (mouseSpeedY == 0)) || 
       ((mouseY < bottomPos - launchAreaHeight - wallHeight + ballRadius) || (mouseY > bottomPos - wallHeight - ballRadius)) ||
       (mouseX < 0) || (mouseSpeedX < 0) || (mouseX > launchAreaWidth + ballRadius)) {
       // delete ball as it is a cancelled launch.
       if (balls.size() > 0) {
         balls.remove(balls.size() - 1);
       }
       ballInPlay=false;
    } else {

      if ((mouseY < topHeight + wallHeight + ballRadius) || (ballYpos < bottomPos - launchAreaHeight + ballRadius)) {
        ballYpos = bottomPos - launchAreaHeight + ballRadius;
      }
      if (mouseX > launchAreaWidth + ballRadius) {
         ballXpos = launchAreaWidth + ballRadius; 
      }
      
      balls.get(balls.size()-1).position.x = ballXpos;
      balls.get(balls.size()-1).position.y = ballYpos;
      
      balls.get(balls.size()-1).velocity.x = mouseSpeedX;
      balls.get(balls.size()-1).velocity.y = mouseSpeedY;
      
      // limit max velocity to avoid ball wall teleporting
      float maxMag = 22;
      if (balls.get(balls.size()-1).velocity.mag() > maxMag) {
        balls.get(balls.size()-1).velocity.mult(maxMag/balls.get(balls.size()-1).velocity.mag());
      }
      
      throwCount++;
      ballInPlay = true; 
    }
  }
}

Ball.pde

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Ball
// © 2024 Brenton Vavrek

class Ball {
  // this class was derived from the "Ball" class on the processing forums: https://processing.org/examples/circlecollision.html
  // the original class outlined only collisions between two "Ball" objects
  // this was expanded to have collision detection between the object and walls
  
  // class state variables
  PVector position;
  PVector velocity;
  int player;
  int r;
  int g;
  int b;

  float radius, m;
  
  // load image once to avoid performance issues
  // AI image created using Image Creator from Microsoft Designer
  PImage img = loadImage("images/basketball.png");

  //ball constructor
  Ball(float x, float y, float r_, int playerNumber, int red, int green, int blue) {
    position = new PVector(x, y);
    velocity = new PVector(7,7);
    velocity.mult(0);
    radius = r_;
    m = radius*.1;
    player = playerNumber;
    r = red;
    g = green;
    b = blue;
  }
  
  // returns which player is playing to change the colors
  int getPlayerNum(){
    return player;
  }
  
  void update() {
    position.add(velocity);
  }
  
  // checks for the screen boundary collisions
  void checkBoundaryCollision(SoundFile sndfile) {
    if (position.x > width-radius) {
      position.x = width-radius;
      velocity.x *= -1;
      sndfile.play();
    } else if (position.x < radius) {
      position.x = radius;
      velocity.x *= -1;
      sndfile.play();
    } else if (position.y > height-radius) {
      position.y = height-radius;
      velocity.y *= -1;
      sndfile.play();
    } else if (position.y < radius) {
      position.y = radius;
      velocity.y *= -1;
      sndfile.play();
    }
  }
  
  // check for barrier wall collisions
 void checkWallCollision(Wall w, SoundFile sndfile) {
    if        ((velocity.x > 0) && (position.x > w.wallX - radius)                && (position.x < w.wallX)                 && (position.y > w.wallY - radius) && (position.y < w.wallY + w.wallHeight + radius)) {
      // left collision
      position.x = w.wallX - radius;
      velocity.x *= -1;
      sndfile.play();
    } else if ((velocity.x < 0) && (position.x < w.wallX + w.wallWidth + radius)  && (position.x > w.wallX + w.wallWidth)   && (position.y > w.wallY - radius) && (position.y < w.wallY + w.wallHeight + radius)) {
      // right collision
      position.x = w.wallX +  w.wallWidth + radius;
      velocity.x *= -1;
      sndfile.play();
    } else if ((velocity.y > 0) && (position.y > w.wallY - radius)                && (position.y < w.wallY)                  && (position.x > w.wallX - radius) && (position.x < w.wallX + w.wallWidth + radius)) {
      // top colision
      position.y = w.wallY - radius;
      velocity.y *= -1;
      sndfile.play();
    } else if ((velocity.y < 0) && (position.y < w.wallY + w.wallHeight + radius) && (position.y > w.wallY + w.wallHeight)   && (position.x > w.wallX - radius) && (position.x < w.wallX + w.wallWidth + radius)) {
      // bottom collison 
      position.y = w.wallY + w.wallHeight + radius;
      velocity.y *= -1;
      sndfile.play();
    }
  }
  
  // apply air friction to the balls to make the movement feel semi-realistic
  void applyAirFriction(){
     velocity.mult(0.9928);
     
     if (abs(velocity.mag()) < 0.15) {
       velocity.mult(0);
     }
  }

  // apply gravity to the balls to make the movement feel semi-realistic
  void applyGravity( int floorY ){
     if ((abs(velocity.x) < 0.05) && (position.y >= (floorY - radius * 1.1))) {
       velocity.y = 0;
     } else if (position.y <= (floorY - radius * 2)){
       velocity.y = velocity.y + 0.25;
     }
  }

  // setter for ball position
  void setposition( int x, int y ){
    position.x = x;
    position.y = y;
  }
  
  // draw ball
  void display() {
    stroke(1);
    strokeWeight(1);
    fill(r, g, b);
    ellipse(position.x, position.y, radius*2, radius*2);
    fill(0);
    stroke(0);
    noStroke();
    textureMode(NORMAL); 
    beginShape();
    texture(img);
    vertex(position.x - radius, position.y - radius , 0, 0);
    vertex(position.x + radius, position.y - radius, 1, 0);
    vertex(position.x + radius, position.y + radius, 1, 1);
    vertex(position.x - radius, position.y + radius, 0, 1);
    endShape();
    stroke(0);
    //noFill();
  }
}

BasketZone.pde

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// BasketZone
// © 2024 Brenton Vavrek

class BasketZone{
  // class state variables
  int scoreValue;
  boolean blockATouched;
  boolean blockBTouched;
  int blockAX;
  int blockAY;
  int blockAWidth;
  int blockAHeight;
  int blockBX;
  int blockBY;
  int blockBWidth;
  int blockBHeight;
  int fillColorR, fillColorG, fillColorB;
  
  // constructor for scorezone
  BasketZone (int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh, int value, int fillColorR_, int fillColorG_, int fillColorB_){
    scoreValue = value;
    blockAX = ax;
    blockAY = ay;
    blockAHeight = ah;
    blockAWidth =aw;
    blockBX = bx;
    blockBY = by;
    blockBHeight = bh;
    blockBWidth = bw;
    fillColorR = fillColorR_;
    fillColorG = fillColorG_;
    fillColorB = fillColorB_;
    blockATouched = false;
    blockBTouched = false;
  }
  
  // check if the ball is in the scorezone
  int checkBasketZone(Ball b, SoundFile sndfile){
    if (b.position.x > blockAX && b.position.x < blockAX + blockAWidth && b.position.y > blockAY && b.position.y < blockAY + blockAHeight){
      blockATouched = true;
    }
    else if (blockATouched == true && b.position.x > blockBX && b.position.x < blockBX + blockBWidth && b.position.y > blockBY && b.position.y < blockBY + blockBHeight) {
      blockBTouched = true;
    }
    
    if (blockATouched && blockBTouched) {
       sndfile.play();
       return scoreValue;
    } else {
      return 0;
    }
  }
  
  // reset the zones after a score registered
  void resetBasketZone() {
    blockATouched = false;
    blockBTouched = false;
  }
  
  
  // draw the scorezone
  void display(){
    fill(fillColorR, fillColorG, fillColorB);
    rect(blockAX, blockAY, blockAWidth, blockAHeight);
    rect(blockBX, blockBY, blockBWidth, blockBHeight);
    strokeWeight(1);
    stroke(1);
    strokeWeight(10);
    fill(0);
    strokeWeight(1);
  }
}

Wall.pde

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Wall                                                                                                                           
// © 2024 Brenton Vavrek

class Wall {
  // class state variables
  int wallHeight;
  int wallWidth;
  int wallX;
  int wallY;
  
  // wall constructor
  Wall(int x, int y, int h, int w){
    wallX = x;
    wallY = y;
    wallHeight = h;
    wallWidth = w;
  }
  
  // draw walls
  void display(){
    noStroke();
    fill(0);
    rect(wallX, wallY, wallWidth, wallHeight);
  }
  
  // draw transparent walls
  void displaytransparent(){
    noStroke();
    fill(0,0,0,0);
    rect(wallX, wallY, wallWidth, wallHeight);
  }
  
}

ScoreBoard.pde

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// ScoreBoard
// © 2024 Brenton Vavrek

class ScoreBoard{
  // class state variables
  int player;
  int playerOneScore = 0;
  int highScore = 0;
  int scoreBoardX;
  int scoreBoardY;
  int scoreBoardWidth;
  int scoreBoardHeight;
  
  //constructor for playerscore
  ScoreBoard (int x, int y, int w, int h){
    scoreBoardX = x;
    scoreBoardY = y;
    scoreBoardWidth = w;
    scoreBoardHeight = h;
  }
  
  // setter for playerscore
  void setPlayerScore(int score){
      playerOneScore = score;
      if (playerOneScore > highScore) {
        highScore =playerOneScore;
      }
  }
  
  // getter for current player score
  int getPlayerScore(){
      return playerOneScore;
  }
  
  // getter for high score
  int getHighScore(){
      return highScore;
  }
  
  // draw scoreboard
  void display(){
    noFill();
    textSize(24);
    fill(255);
    rect(scoreBoardX, scoreBoardY, scoreBoardWidth, scoreBoardHeight);
    fill(204, 85, 0);
    textAlign(CENTER,CENTER);
    text("Player: " + this.getPlayerScore(), scoreBoardX + (scoreBoardWidth * 0.25), scoreBoardY + (scoreBoardHeight/2));
    fill(0);
    text("High: " + this.getHighScore(), scoreBoardX + (scoreBoardWidth * 0.75), scoreBoardY + (scoreBoardHeight/2));
  }
}

BANK-IT-BALL P5.JS JAVASCRIPT CODE TRANSLATION

Press [SHIFT]+[Mousewheel] to scroll left and right.

Java to JavaScript code translation was assisted partially by Microsoft Co-Pilot.

 

There were also some code tweaks to support an online p5.js web version. A global preloading of the basketball image was done to improve animation performance and a scale factor reduction was applied to reduce the overall game size for embedding into a browser. Image coordinate translation was also done to align elements to the alternate JavaScript game canvas origin.

BankItBall.js

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
// Bank-It-Ball
// © 2024-2025 Brenton Vavrek

// Ball Class
class Ball {
  constructor(x, y, r_, playerNumber, red, green, blue) {
    this.position = createVector(x, y);
    this.velocity = createVector(7, 7);
    this.velocity.mult(0);
    this.radius = r_ * scaleFactor;
    this.m = this.radius * 0.1;
    this.player = playerNumber;
    this.r = red;
    this.g = green;
    this.b = blue;
    this.img = ballimg; // global preload for performance
  }

  getPlayerNum() {
    return this.player;
  }

  update() {
    this.position.add(this.velocity);
  }

  checkBoundaryCollision(sndfile) {
    if (this.position.x > width - this.radius) {
      this.position.x = width - this.radius;
      this.velocity.x *= -1;
      sndfile.play();
    } else if (this.position.x < this.radius) {
      this.position.x = this.radius;
      this.velocity.x *= -1;
      sndfile.play();
    } else if (this.position.y > height - this.radius) {
      this.position.y = height - this.radius;
      this.velocity.y *= -1;
      sndfile.play();
    } else if (this.position.y < this.radius) {
      this.position.y = this.radius;
      this.velocity.y *= -1;
      sndfile.play();
    }
  }

  checkWallCollision(w, sndfile) {
    if ((this.velocity.x >= 0)        && (this.position.x > w.wallX - this.radius)                && (this.position.x < w.wallX)                && (this.position.y > w.wallY - this.radius)         && (this.position.y < w.wallY + w.wallHeight + this.radius)) {
      // Left collision
      this.position.x = w.wallX - this.radius;
      this.velocity.x *= -1;
      sndfile.play();
    } else if ((this.velocity.x <= 0) && (this.position.x < w.wallX + w.wallWidth + this.radius)  && (this.position.x > w.wallX + w.wallWidth)  && (this.position.y > w.wallY - this.radius)         && (this.position.y < w.wallY + w.wallHeight + this.radius)) {
      // Right collision
      this.position.x = w.wallX + w.wallWidth + this.radius;
      this.velocity.x *= -1;
      sndfile.play();
    } else if ((this.velocity.y >= 0) && (this.position.y > w.wallY - this.radius)                && (this.position.y < w.wallY)                && (this.position.x > w.wallX - this.radius)        && (this.position.x < w.wallX + w.wallWidth + this.radius)) {
      // Top collision
      this.position.y = w.wallY - this.radius;
      this.velocity.y *= -1;
      sndfile.play();
    } else if ((this.velocity.y <= 0) && (this.position.y < w.wallY + w.wallHeight + this.radius) && (this.position.y > w.wallY + w.wallHeight) && (this.position.x > w.wallX - this.radius)        && this.position.x < (w.wallX + w.wallWidth + this.radius)) {
      // Bottom collision 
      this.position.y = w.wallY + w.wallHeight + this.radius;
      this.velocity.y *= -1;
      sndfile.play();
    }
  }

  applyAirFriction() {
    this.velocity.mult(0.9928);
    if (abs(this.velocity.mag()) < 0.15) {
      this.velocity.mult(0);
    }
  }

  applyGravity(floorY) {
    if ((abs(this.velocity.x) < 0.05) && (this.position.y >= (floorY - this.radius * 1.1))) {
      this.velocity.y = 0;
    } else if (this.position.y <= (floorY - this.radius * 2)) {
      this.velocity.y += 0.25;
    }
  }

  setposition(x, y) {
    this.position.x = x;
    this.position.y = y;
  }

  display() {
    stroke(1);
    strokeWeight(1);
    fill(this.r, this.g, this.b);
    ellipse(this.position.x, this.position.y, this.radius * 2 * scaleFactor, this.radius * 2 * scaleFactor);
    fill(0);
    stroke(0);
    noStroke();
    textureMode(NORMAL);
    beginShape();
    texture(this.img);
    vertex(this.position.x - this.radius, this.position.y - this.radius, 0, 0);
    vertex(this.position.x + this.radius, this.position.y - this.radius, 1, 0);
    vertex(this.position.x + this.radius, this.position.y + this.radius, 1, 1);
    vertex(this.position.x - this.radius, this.position.y + this.radius, 0, 1);
    endShape();
    stroke(0);
  }
}

// BasketZone Class
class BasketZone {
  constructor(ax, ay, aw, ah, bx, by, bw, bh, value, fillColorR_, fillColorG_, fillColorB_) {
    this.scoreValue = value;
    this.blockAX = ax; this.blockAY = ay; this.blockAHeight = ah; this.blockAWidth = aw;
    this.blockBX = bx; this.blockBY = by; this.blockBHeight = bh; this.blockBWidth = bw;
    this.fillColorR = fillColorR_; this.fillColorG = fillColorG_; this.fillColorB = fillColorB_;
    this.blockATouched = false;
    this.blockBTouched = false;
  }

  checkBasketZone(b, sndfile) {
    if (b.position.x > this.blockAX && b.position.x < this.blockAX + this.blockAWidth && b.position.y > this.blockAY && b.position.y < this.blockAY + this.blockAHeight) {
      this.blockATouched = true;
    } else if (this.blockATouched && b.position.x > this.blockBX && b.position.x < this.blockBX + this.blockBWidth && b.position.y > this.blockBY && b.position.y < this.blockBY + this.blockBHeight) {
      this.blockBTouched = true;
    }
    
    if (this.blockATouched && this.blockBTouched) {
      sndfile.play();
      return this.scoreValue;
    } else {
      return 0;
    }
  }

  resetBasketZone() {
    this.blockATouched = false;
    this.blockBTouched = false;
  }

  display() {
    fill(this.fillColorR, this.fillColorG, this.fillColorB);
    rect(this.blockAX, this.blockAY, this.blockAWidth, this.blockAHeight);
    rect(this.blockBX, this.blockBY, this.blockBWidth, this.blockBHeight);
    strokeWeight(1);
    stroke(1);
    strokeWeight(10);
    fill(0);
    strokeWeight(1);
  }
}

// ScoreBoard Class
class ScoreBoard {
  constructor(x, y, w, h) {
    this.playerOneScore = 0;
    this.highScore = 0;
    this.scoreBoardX = x;
    this.scoreBoardY = y;
    this.scoreBoardWidth = w;
    this.scoreBoardHeight = h;
  }

  setPlayerScore(score) {
    this.playerOneScore = score;
    if (this.playerOneScore > this.highScore) {
      this.highScore = this.playerOneScore;
    }
  }

  getPlayerScore() {
    return this.playerOneScore;
  }

  getHighScore() {
    return this.highScore;
  }

  display() {
    noFill();
    textSize(24);
    fill(255);
    rect(this.scoreBoardX, this.scoreBoardY, this.scoreBoardWidth, this.scoreBoardHeight);
    fill(204, 85, 0);
    textAlign(CENTER, CENTER);
		textSize(24 * scaleFactor);
    text("Player: " + this.getPlayerScore(), this.scoreBoardX + (this.scoreBoardWidth * 0.25), this.scoreBoardY + (this.scoreBoardHeight / 2.5));
    fill(0);
    text("High: " + this.getHighScore(), this.scoreBoardX + (this.scoreBoardWidth * 0.75), this.scoreBoardY + (this.scoreBoardHeight / 2.5));
  }
}

// Wall Class
class Wall {
  constructor(x, y, h, w) {
    this.wallX = x;
    this.wallY = y;
    this.wallHeight = h;
    this.wallWidth = w;
  }

  display() {
    noStroke();
    fill(0);
    rect(this.wallX, this.wallY, this.wallWidth, this.wallHeight);
  }

  displaytransparent() {
    noStroke();
    fill(0, 0, 0, 0);
    rect(this.wallX, this.wallY, this.wallWidth, this.wallHeight);
  }
}

let scaleFactor = 0.7;
let screenWidth = 1280 * scaleFactor;
let screenHeight = 720 * scaleFactor;
let topHeight = 100;

// board variables
let wallX = 0;
let wallY = 360;
let wallHeight = 29 * scaleFactor;
let wallWidth = 600;
let bottomPos = screenHeight - 3 * wallHeight;
let ceilingHeight = bottomPos - topHeight;
let launchAreaWidth = Math.floor(ceilingHeight * 0.6);
let launchAreaHeight = Math.floor(ceilingHeight * 0.6);
let ballRadius = 26;
let mouseSpeedX = 0;
let mouseSpeedY = 0;
let basketHeight = topHeight + Math.floor(ceilingHeight * 0.4);
let basketRimX = Math.floor(wallX + screenWidth * 0.90);
let basketWidth = screenWidth - basketRimX;

// font variable
let GameFont;

// player variables
let activePlayer = 1;
let playerOneR = 204;
let playerOneG = 85;
let playerOneB = 0;

// score block variables
let basketZoneHeight = 20;
let basketZoneWidth = basketWidth;
let basketZoneAX = basketRimX;
let basketZoneAY = basketHeight;
let basketZoneBX = basketRimX;
let basketZoneBY = basketHeight + basketZoneHeight;
let basketZoneR = 0;
let basketZoneG = 255;
let basketZoneB = 0;

// score board variables
let scoreBoardWidth = screenWidth/4;
let scoreBoardHeight = topHeight/2 * scaleFactor;
let scoreBoardX = screenWidth/2 - scoreBoardWidth/2;
let scoreBoardY = topHeight/4;
let updatedPlayerOneScore = 0;
let playerOneBasket = false;

// new game button variables
let buttonX = screenWidth/5;
let buttonY = topHeight/4;
let buttonWidth = screenWidth/8;
let buttonHeight = topHeight/2 * scaleFactor;

// general game variables
let gameOver = false;
let ballInPlay = false;
let ballMaxCount = 6;
let throwCount = 0;
let frameCounter = 0;
let ballimg;

// mouse variables
let mouseHold = false;

// file media variables
let img, img2;
let sndfilebounce, sndfilewhoosh, sndfilebuzzer, sndfilewhistle;

let buzzerPlayed = false;

let balls = [];
let walls = [];
let zones = [];
let scoreboard;

function preload() {
  img = loadImage('gym.jpg');
  img2 = loadImage('basketballnet2.png');
	ballimg = loadImage('basketball.png');
  sndfilebounce = loadSound('ball-bounce.mp3');
  sndfilewhoosh = loadSound('whoosh.mp3');
  sndfilebuzzer = loadSound('buzzer.mp3');
  sndfilewhistle = loadSound('whistle.mp3');
  // Load the font from Google Fonts
  loadFont('ProcessingSans-Bold.ttf', function(font) {
    GameFont = font;
  });
}

function setup() {
  createCanvas(screenWidth, screenHeight, WEBGL);
  background(255);
  textFont(GameFont); // Set the font for your text
	
	// Initialize game objects
  walls = [
    new Wall(wallX, topHeight, wallHeight, screenWidth),
    new Wall(wallX, bottomPos, wallHeight, screenWidth),
    new Wall(Math.floor(wallX + screenWidth * 0.6), topHeight, Math.floor(ceilingHeight * 0.6 + wallHeight/2), wallHeight),
    new Wall(Math.floor(wallX + screenWidth * 0.4), topHeight + Math.floor(ceilingHeight * 0.4), Math.floor(ceilingHeight * 0.6 + wallHeight/2), wallHeight),
    new Wall(basketRimX, basketHeight, wallHeight * 2, wallHeight)
  ];
  
  zones = [
    new BasketZone(basketZoneAX, basketZoneAY, basketZoneWidth, basketZoneHeight, 
    basketZoneBX, basketZoneBY, basketZoneWidth, basketZoneHeight, 1, basketZoneR, basketZoneG, basketZoneB)
  ];
  
  scoreboard = new ScoreBoard(scoreBoardX, scoreBoardY, scoreBoardWidth, scoreBoardHeight);
  
  sndfilewhistle.play();
}

function draw() {
  mouseSpeedX = mouseX - pmouseX;
  mouseSpeedY = mouseY - pmouseY;
  background(220);
  
  resetStyle();
  
 // Translate to the upper right corner of your "visible area"
  translate(-screenWidth/2, -screenHeight/2, 0); // Move to top-left of your area
	
  // draw game name
  stroke(10);
  textAlign(CENTER, CENTER);
  fill(204, 85, 0);
	textSize(24 * scaleFactor);
  text('Bank-it-Ball', buttonX/2, buttonY + (buttonHeight/2));
  fill(0);
	textSize(24 * scaleFactor);
  text('Bank-it-Ball', buttonX/2 + 2, buttonY + (buttonHeight/2) + 2);
  
  resetStyle();
	
  // draw gym background
  fill(255, 242, 229);
  rect(wallX, topHeight, screenWidth, ceilingHeight);
	
  noStroke();
  textureMode(NORMAL); 
  beginShape();
  texture(img);
  vertex(wallX, topHeight, 0, 0);
  vertex(screenWidth, topHeight, 1, 0);
  vertex(screenWidth, bottomPos, 1, 1);
  vertex(wallX, bottomPos, 0, 1);
  endShape();

  resetStyle();

  // draw launch area
  fill(255, 229, 204, 100);
  noStroke();
  rect(wallX, bottomPos - launchAreaHeight, launchAreaWidth, launchAreaHeight);
  textAlign(CENTER, CENTER);
  fill(255);
	textSize(24 * scaleFactor);
  text('THROW ZONE (CLICK)', launchAreaWidth/2, bottomPos - launchAreaHeight/2);

  resetStyle();
  
  // draw backboard
  fill(205);
  let backboardWidth = 5 * scaleFactor;
  let backboardHeight = 150 * scaleFactor;
  rect(screenWidth - backboardWidth, basketHeight - backboardHeight, backboardWidth, backboardHeight);
  
  // draw new game button and check for hover
  if (mouseX > buttonX && mouseX < buttonX + buttonWidth && mouseY > buttonY && mouseY < buttonY + buttonHeight) {
    fill(240);
  } else {
    fill(205);
  }
  strokeWeight(5);
  rect(buttonX, buttonY, buttonWidth, buttonHeight);
  strokeWeight(1);
  fill(1);
  textFont(GameFont);
	textSize(24 * scaleFactor);
  text('New Game', buttonX + (buttonWidth/2), buttonY + (buttonHeight/2.5));
  
  resetStyle();
  
  // check for the ball count
  if (throwCount + 1 > ballMaxCount && !mouseHold) {
    gameOver = true;
  }

  // display walls except virtual rim wall
  for (let i = 0; i < walls.length - 1; i++) {
    walls[i].display();
  }
  
  // display virtual rim wall as transparent
  walls[walls.length - 1].displaytransparent();
  
  resetStyle();
  
  // display the scoreboard
  scoreboard.display();
  
  // ball physics
  for (let b of balls) {
    b.applyGravity(bottomPos);
    b.applyAirFriction();
    b.checkBoundaryCollision(sndfilebounce);
    for (let w of walls) {
      b.checkWallCollision(w, sndfilebounce);
    }
    b.update();
    b.display();
  }
  
  // Check for mouse button holding
  if (mouseHold && !gameOver && !ballInPlay) {
    if (mouseX > wallX + ballRadius && mouseX < launchAreaWidth - ballRadius && mouseY < bottomPos - ballRadius && mouseY > launchAreaHeight + ballRadius) {
      balls[balls.length - 1] = new Ball(mouseX, mouseY, ballRadius, activePlayer, playerOneR, playerOneG, playerOneB);
		}
  }
  
  // Update scoreboard
  let playerOneTurnScore = 0;
  for (let b of balls) {
    for (let z of zones) {
      if (!playerOneBasket) {
        playerOneTurnScore = z.checkBasketZone(b, sndfilewhoosh);
      }
      if (playerOneTurnScore === 1) {
        playerOneBasket = true;
      }
    }
  }
  
  // Update scores and scoreboard display
  updatedPlayerOneScore += playerOneTurnScore;
  scoreboard.setPlayerScore(updatedPlayerOneScore);
  scoreboard.display();
  
  // Check if balls are in motion
  ballInPlay = balls.some(b => b.velocity.x !== 0 || b.velocity.y !== 0);

  if (!ballInPlay) {
    if (gameOver) {
      if (!buzzerPlayed) {
        sndfilebuzzer.play();
        buzzerPlayed = true;
      }
      if (scoreboard.getPlayerScore() > scoreboard.getHighScore()) {
        fill(255, 0, 0);
				textSize(24 * scaleFactor);
        text('GAME OVER  |  New High Score', (scoreBoardX + scoreBoardWidth) + (screenWidth - (scoreBoardX + scoreBoardWidth))/2, buttonY + (buttonHeight/2));
      } else {
        fill(135, 31, 120);
				textSize(24 * scaleFactor);
        text('GAME OVER', (scoreBoardX + scoreBoardWidth) + (screenWidth - (scoreBoardX + scoreBoardWidth))/2, buttonY + (buttonHeight/2));
      }
    } else {
      fill(playerOneR, playerOneG, playerOneB);
			textSize(24 * scaleFactor);
      text('PLAYER  |  Shot ' + (throwCount + 1) + ' of ' + ballMaxCount, (scoreBoardX + scoreBoardWidth) + (screenWidth - (scoreBoardX + scoreBoardWidth))/2, buttonY + (buttonHeight/2));
    }
  }
  
  // Remove balls if necessary
  let deleteBall = balls.some(b => (!mouseHold && ballInPlay && b.velocity.mag() < 10 && b.position.y > bottomPos - ceilingHeight * 0.25) || b.position.y > bottomPos || b.position.y < topHeight);
  
  if (deleteBall) {
    ballInPlay = false;
    balls.shift(); // Remove the first ball in the array
  }

  // Draw basket netting
  fill(255, 255, 255, 1);
  noStroke();
  textureMode(NORMAL); 
  beginShape();
  texture(img2);
  vertex(basketRimX, basketHeight , 0, 0);
  vertex(basketRimX + basketWidth, basketHeight, 1, 0);
  vertex(basketRimX + basketWidth, basketHeight + basketWidth * 0.75, 1, 1);
  vertex(basketRimX, basketHeight + basketWidth * 0.75, 0, 1);
  endShape();

  resetStyle();
}

function resetStyle() {
  fill(0);
  stroke(0);
  strokeWeight(1);
}

function mousePressed() {
  if (!ballInPlay && !gameOver && mouseX > wallX + ballRadius && mouseX < launchAreaWidth + ballRadius && mouseY < bottomPos - ballRadius && mouseY > bottomPos - launchAreaHeight + ballRadius) {
    if (balls.length > 0) {
      balls.pop();
    }
    if (!mouseHold) {
      balls.push(new Ball(mouseX, mouseY, ballRadius, activePlayer, playerOneR, playerOneG, playerOneB));
    }
    for (let z of zones) {
      z.resetBasketZone();
    }
    playerOneBasket = false;
    mouseHold = true;
  }
  
  // New game button logic
  if (mouseX > buttonX && mouseX < buttonX + buttonWidth && mouseY > buttonY && mouseY < buttonY + buttonHeight) {
    balls = [];
    gameOver = false;
    throwCount = 0;
    activePlayer = 1;
    ballInPlay = false;
    updatedPlayerOneScore = 0;
    playerOneBasket = false;
    for (let z of zones) {
      z.resetBasketZone();
    }
    walls[2].wallX = Math.floor(random(obstacleOneStartX - obstacleWiggleX, obstacleOneStartX + obstacleWiggleX));
    walls[3].wallX = Math.floor(random(obstacleTwoStartX - obstacleWiggleX, obstacleTwoStartX + obstacleWiggleX));
    
    buzzerPlayed = false;
    sndfilewhistle.play();
  }
	
}

function mouseReleased() {
  if (mouseHold && !gameOver) {
    mouseHold = false;
    let ballXpos = mouseX;
    let ballYpos = mouseY;

    if ((mouseSpeedX === 0 && mouseSpeedY === 0) || 
        (mouseY < bottomPos - launchAreaHeight - wallHeight + ballRadius || mouseY > bottomPos - wallHeight - ballRadius) ||
        mouseX < 0 || mouseSpeedX < 0 || mouseX > launchAreaWidth + ballRadius) {
      if (balls.length > 0) {
        balls.pop();
      }
      ballInPlay = false;
    } else {
      if (mouseY < topHeight + wallHeight + ballRadius || ballYpos < bottomPos - launchAreaHeight + ballRadius) {
        ballYpos = bottomPos - launchAreaHeight + ballRadius;
      }
      if (mouseX > launchAreaWidth + ballRadius) {
        ballXpos = launchAreaWidth + ballRadius; 
      }
      if (balls.length > 0) {
        let lastBall = balls[balls.length - 1];
        lastBall.position.x = ballXpos;
        lastBall.position.y = ballYpos;
        lastBall.velocity.x = mouseSpeedX;
        lastBall.velocity.y = mouseSpeedY;
        
        let maxMag = 22;
        if (lastBall.velocity.mag() > (maxMag * scaleFactor)) {
          lastBall.velocity.mult((maxMag * scaleFactor) / lastBall.velocity.mag());
        }
        
        throwCount++;
        ballInPlay = true;
      }
    }
  }
}

let obstacleOneStartX = Math.floor(wallX + screenWidth * 0.6);
let obstacleTwoStartX = Math.floor(wallX + screenWidth * 0.4);
let obstacleWiggleX = Math.floor(wallX + screenWidth * 0.05);