

/*
Pong by Bjorn Carlin
Applet released September 2000
Source released December 2001

This source code is in the public domain,
you may use it in your own projects. Please
give credit where credit is due.

Applet works best with (width,height) = (300,200)

Pong requires GameApplet or GameForm
*/


import java.awt.*;
import java.awt.event.*;


public class Pong extends GameApplet implements MouseListener {
	final int						kUp = 0;
	final int						kDown = 1;
	final int						kNewBall = 2;
	
	final int						kPaddleRadius = 25;
	final int						kBallRadius = 5;
	final int						kPaddleWidth = 7;
	final int						kPaddleAboveSurface = 15;
	final int						kTopWallWidth = 20;
	final int						kBottomWallWidth = 20;
	final int						kLevelCount = 10;
	
	int								cBallMaxMove;
	int								cBallMoveIncrease;
	int								cLeftPaddleMaxSpeed;
	int								cRightPaddleMaxSpeed;
	int								cStartMoveX;
	int								cStartMoveY;
	
	int								cLeftPaddleX; // in 1/1024 pixels
	int								cLeftPaddleY; // in 1/1024 pixels
	int								cLeftScore;
	int								cRightPaddleX; // in 1/1024 pixels
	int								cRightPaddleY; // in 1/1024 pixels
	int								cRightScore;
	
	int								cBallX; // in 1/1024 pixels
	int								cBallY; // in 1/1024 pixels
	int								cBallMoveX; // in 1/1024 pixels
	int								cBallMoveY; // in 1/1024 pixels
	
	int								cLevel;
	boolean							cDrawTitleScreen;
	boolean							cBallBouncing;
	
	
	public void init() {
		super.init(kDoubleBuffer);
		
		if (!setKeys(5, 3,
				new int[] {KeyEvent.VK_UP, KeyEvent.VK_DOWN, KeyEvent.VK_SPACE},
				new int[] {kKeyContinous, kKeyContinous, kKeyOnce}))
			System.out.println("failed key init");
		
		cFrameLength = 8; // 125 fps
		
		addMouseListener(this);
		
		cBufferGraphics.setFont(new Font("Serif", Font.PLAIN, 12));
	}
	
	public void start() {
		super.start();
		
		cLeftPaddleX = (kPaddleAboveSurface) << 10;
		cLeftPaddleY = (getHeight() / 2) << 10;
		
		cRightPaddleX = (getWidth() - kPaddleAboveSurface) << 10;
		cRightPaddleY = (getHeight() / 2) << 10;
		
		cBallX = (getWidth() / 2) << 10;
		cBallY = (getHeight() / 2) << 10;
		
		newGame();
	}
	
	public void newGame() {
		if (cGameInProgress)
			return;
		
		super.newGame();
		cBallBouncing = false;
		cLeftScore = 0;
		cRightScore = 0;
		setLevel(1);
	}
	
	protected void advanceFrame() {
		if (isKeyActive(kNewBall) && !cBallBouncing) {
			if (cLeftScore == 11 || cRightScore == 11) {
				cLeftScore = 0;
				cRightScore = 0;
			}
			
			// start ball movement
			cBallBouncing = true;
			if ((cLeftScore + cRightScore) % 2 == 0)
				cBallMoveX = cStartMoveX;
			else
				cBallMoveX = -cStartMoveX;
			
			cBallMoveY = (int) (cStartMoveY - Math.random() * cStartMoveY * 2);
			
			cDrawTitleScreen = false;
		}
		
		// move left paddle
		if (isKeyActive(kUp))
			cLeftPaddleY -= cLeftPaddleMaxSpeed;
		if (isKeyActive(kDown))
			cLeftPaddleY += cLeftPaddleMaxSpeed;
		
		if ((cLeftPaddleY >> 10) - kPaddleRadius < kTopWallWidth)
			cLeftPaddleY = (kTopWallWidth + kPaddleRadius) << 10;
		if ((cLeftPaddleY >> 10) + kPaddleRadius > getHeight() - kBottomWallWidth)
			cLeftPaddleY = (getHeight() - kBottomWallWidth - kPaddleRadius) << 10;
		
		// move right paddle
		if ((cRightPaddleY >> 10) - kPaddleRadius / 2 > (cBallY >> 10))
			cRightPaddleY -= cRightPaddleMaxSpeed;
		else if ((cRightPaddleY >> 10) + kPaddleRadius / 2 < (cBallY >> 10))
			cRightPaddleY += cRightPaddleMaxSpeed;
		
		if ((cRightPaddleY >> 10) - kPaddleRadius < kTopWallWidth)
			cRightPaddleY = (kTopWallWidth + kPaddleRadius) << 10;
		if ((cRightPaddleY >> 10) + kPaddleRadius > getHeight() - kBottomWallWidth)
			cRightPaddleY = (getHeight() - kBottomWallWidth - kPaddleRadius) << 10;
		
		if (cBallBouncing) {
			// move ball
			cBallX += cBallMoveX;
			cBallY += cBallMoveY;
			
			// check for wall bounces
			if ((cBallY >> 10) - kBallRadius < kTopWallWidth) {
				cBallMoveY = -cBallMoveY;
				cBallY = (kTopWallWidth + kBallRadius) << 10;
			}
			if ((cBallY >> 10) + kBallRadius > getHeight() - kBottomWallWidth) {
				cBallMoveY = -cBallMoveY;
				cBallY = (getHeight() - kBottomWallWidth - kBallRadius) << 10;
			}
			
			// check for left paddle bounces
			if (((cBallX >> 10) - kBallRadius < (cLeftPaddleX >> 10)) &&
					Math.abs((cBallY - cLeftPaddleY) >> 10) < kBallRadius + kPaddleRadius) {
				cBallMoveX = -cBallMoveX + cBallMoveIncrease;
				cBallX = cLeftPaddleX + (kBallRadius << 10);
				
				if (cBallMoveX > cBallMaxMove)
					cBallMoveX = cBallMaxMove;
				
				cBallMoveY = (Math.abs(cBallMoveX) * ((cBallY - cLeftPaddleY) >> 10)) / kPaddleRadius;
				if (cBallMoveY > cBallMaxMove)
					cBallMoveY = cBallMaxMove;
				if (cBallMoveY < -cBallMaxMove)
					cBallMoveY = -cBallMaxMove;
			}
			
			// check for right paddle bounces
			if (((cBallX >> 10) + kBallRadius > (cRightPaddleX >> 10)) &&
					Math.abs((cBallY - cRightPaddleY) >> 10) < kBallRadius + kPaddleRadius) {
				cBallMoveX = -cBallMoveX - cBallMoveIncrease;
				cBallX = cRightPaddleX - (kBallRadius << 10);
				
				if (cBallMoveX < -cBallMaxMove)
					cBallMoveX = -cBallMaxMove;
				
				cBallMoveY = (Math.abs(cBallMoveX) * ((cBallY - cRightPaddleY) >> 10)) / kPaddleRadius;
				if (cBallMoveY > cBallMaxMove)
					cBallMoveY = cBallMaxMove;
				if (cBallMoveY < -cBallMaxMove)
					cBallMoveY = -cBallMaxMove;
			}
			
			// check for leaving side of table
			if (((cBallX >> 10) < kBallRadius) ||
					(getWidth() - (cBallX >> 10) < kBallRadius)) {
				
				if ((cBallX >> 10) < kBallRadius)
					cRightScore++;
				else
					cLeftScore++;
				
				cBallX = (getWidth() / 2) << 10;
				cBallY = (getHeight() / 2) << 10;
				cBallBouncing = false;
			}
		}
	}
	
	public void paint(Graphics theGraphics) {
		update(theGraphics);
	}
	
	public void update(Graphics theGraphics) {
		// draw background and walls
		cBufferGraphics.setColor(Color.black);
		cBufferGraphics.fillRect(0, kTopWallWidth, getWidth(), getHeight() - kTopWallWidth - kBottomWallWidth);
		cBufferGraphics.setColor(Color.lightGray);
		cBufferGraphics.fillRect(0, 0, getWidth(), kTopWallWidth);
		cBufferGraphics.fillRect(0, getHeight() - kBottomWallWidth, getWidth(), kBottomWallWidth);
		
		// draw paddles and ball
		cBufferGraphics.setColor(Color.white);
		cBufferGraphics.fillRect((cLeftPaddleX >> 10) - kPaddleWidth, (cLeftPaddleY >> 10) - kPaddleRadius, kPaddleWidth, 2 * kPaddleRadius);
		cBufferGraphics.fillRect((cRightPaddleX >> 10), (cRightPaddleY >> 10) - kPaddleRadius, kPaddleWidth, 2 * kPaddleRadius);
		
		if (!cGameInProgress || !cBallBouncing)
			cBufferGraphics.setColor(Color.darkGray); // dim ball
		cBufferGraphics.fillOval((cBallX >> 10) - kBallRadius, (cBallY >> 10) - kBallRadius, 2 * kBallRadius, 2 * kBallRadius);
		
		// draw score
		cBufferGraphics.setColor(Color.black);
		cBufferGraphics.drawString("" + cLeftScore + " - " + cRightScore,
			(getWidth() - cBufferGraphics.getFontMetrics().stringWidth("" + cLeftScore + " - " + cRightScore)) / 2, getHeight() - 4);
		
		if (!cBallBouncing) {
			// draw level select
			cBufferGraphics.setColor(Color.black);
			cBufferGraphics.drawString("Level", 5, kTopWallWidth - 4);
			
			for (int levelCounter = 1; levelCounter <= kLevelCount; levelCounter++) {
				if (cLevel == levelCounter)
					cBufferGraphics.setColor(Color.black);
				else
					cBufferGraphics.setColor(Color.gray);
				cBufferGraphics.drawString("" + levelCounter, 40 + 20 * levelCounter, kTopWallWidth - 4);
			}
			
			cBufferGraphics.setColor(Color.white);
			cBufferGraphics.drawString("press space to start", (getWidth() - cBufferGraphics.getFontMetrics().stringWidth("press space to start")) / 2, 160);
		}
		
		if (!cBallBouncing) {
			cBufferGraphics.setColor(Color.white);
			cBufferGraphics.setFont(new Font("Serif", Font.PLAIN, 18));
			cBufferGraphics.drawString("Return of the",
				(getWidth() - cBufferGraphics.getFontMetrics().stringWidth("Return of the")) / 2, 45);
			cBufferGraphics.setFont(new Font("Serif", Font.PLAIN, 48));
			cBufferGraphics.drawString("Pong",
				(getWidth() - cBufferGraphics.getFontMetrics().stringWidth("Pong")) / 2, 98);
			cBufferGraphics.setFont(new Font("Serif", Font.PLAIN, 14));
			cBufferGraphics.drawString("by Bjšrn Carlin",
				(getWidth() - cBufferGraphics.getFontMetrics().stringWidth("by Bjšrn Carlin")) / 2, 130);
			
			cBufferGraphics.setFont(new Font("Serif", Font.PLAIN, 12));
		}
		else if (cLeftScore == 11) {
			cBufferGraphics.setColor(Color.white);
			cBufferGraphics.setFont(new Font("Serif", Font.PLAIN, 30));
			cBufferGraphics.drawString("Left Player Wins",
				(getWidth() - cBufferGraphics.getFontMetrics().stringWidth("Left Player Wins")) / 2, 80);
			cBufferGraphics.drawString("" + cLeftScore + " to " + cRightScore,
				(getWidth() - cBufferGraphics.getFontMetrics().stringWidth("" + cLeftScore + " to " + cRightScore)) / 2, 135);
			
			cBufferGraphics.setFont(new Font("Serif", Font.PLAIN, 12));
		}
		else if (cRightScore == 11) {
			cBufferGraphics.setColor(Color.white);
			cBufferGraphics.setFont(new Font("Serif", Font.PLAIN, 30));
			cBufferGraphics.drawString("Right Player Wins",
				(getWidth() - cBufferGraphics.getFontMetrics().stringWidth("Right Player Wins")) / 2, 80);
			cBufferGraphics.drawString("" + cRightScore + " to " + cLeftScore,
				(getWidth() - cBufferGraphics.getFontMetrics().stringWidth("" + cRightScore + " to " + cLeftScore)) / 2, 135);
			
			cBufferGraphics.setFont(new Font("Serif", Font.PLAIN, 12));
		}
		
		// copy to screen
		theGraphics.drawImage(cBuffer, 0, 0, null);
	}
	
	public void setLevel(int level) {
		if (level >= 1 && level <= kLevelCount) {
			cBallMaxMove = 4000;
			cBallMoveIncrease = 90 + 6 * level;
			cLeftPaddleMaxSpeed = 2000;
			cRightPaddleMaxSpeed = 400 + 200 * level;
			cStartMoveX = 800 + 140 * level;
			cStartMoveY = 150 + 40 * level;
			
			cLevel = level;
		}
	}
	
	public void mousePressed(MouseEvent theEvent) {
		if (!cBallBouncing && theEvent.getY() <= kTopWallWidth) {
			setLevel((theEvent.getX() - 34) / 20);
			repaint();
		}
	}
	
	public void mouseEntered(MouseEvent theEvent) {}
	public void mouseExited(MouseEvent theEvent) {}
	public void mouseClicked(MouseEvent theEvent) {}
	public void mouseReleased(MouseEvent theEvent) {}
}

