Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/401.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java pong游戏图形或geom 2D_Java_Awt_Pong - Fatal编程技术网

Java pong游戏图形或geom 2D

Java pong游戏图形或geom 2D,java,awt,pong,Java,Awt,Pong,我的第一个项目是用Java制作游戏Pong。 因为我需要计算我的x位置和y位置的增量,所以我认为最好的方法是使用双变量(如果我错了,请原谅)。但是不能使用double来填充libjava.awt.*中的形状 在这里,我计算球击到桨后的角度 int c = (int) Math.atan2(ball.getPosY(), ball.getPosX()); int delta_x = (int) (1 * Math.cos(c)); int delta_y = (int) (1 * Mat

我的第一个项目是用Java制作游戏Pong。 因为我需要计算我的
x
位置和
y
位置的增量,所以我认为最好的方法是使用双变量(如果我错了,请原谅)。但是不能使用double来填充lib
java.awt.*
中的形状

在这里,我计算球击到桨后的角度

int c = (int) Math.atan2(ball.getPosY(), ball.getPosX());
int delta_x = (int) (1 * Math.cos(c));

    int delta_y = (int) (1 * Math.sin(c));
    this.dx += delta_x;
    this.dy += delta_y;
dx
dy
我改变了乒乓球
x
y
的位置。 我在这里画我的乒乓球

g.setColor(Color.WHITE);
g.fillOval(this.posX, this.posY, 25, 25);
如果我想使
dx
dy
更精确,我必须将
delta_y
delta_x
的类型更改为双精度。
但是
fillOval()
不适用于双变量。因此,我是否必须在
geom.Point2D.Double
中生成图形?

对delta使用双值,但将最终坐标转换为int

像这样(在伪代码样式中):

编辑:对不起,我意识到一个错误。如果三角形足够小,球就不会移动!因此,你也需要将球的坐标存储在双打中,并且只有在最后绘制球时才施展。。。对不起


编辑2:注意上面的代码是不可编译的。您需要更多的代码(如实际的x-y字段、jframe代码等)

以下是Erin W的代码。他使用2D图形:

/*
 * Copyright (c) 2007 Eric Woroshow
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package ca.ericw.pong;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;

public class Pong {

    public static final String GAME_NAME = "PONG!";
    private static final int GAME_WIDTH = 320;
    private static final int GAME_HEIGHT = 240;
    private static final int GAME_FPS = 60;
    private static final int PADDLE_HEIGHT = 60;
    private static final int PADDLE_HALFWIDTH = 5;
    private static final float PADDLE_SPEED = 2f;
    private static final float BALL_RADIUS = 5;
    private static final float BALL_SPEED_INCREASE = 1.05f;
    private static final int GFX_SPACER = 10;
    private static final int SCORE_TO_WIN = 5;
    private static final int INTERPOINT_DELAY = GAME_FPS;
    private static final int INTERGAME_DELAY = 3 * GAME_FPS;

    private Frame window;
    private BufferStrategy bufStrat;
    private boolean[] keys;

    private Font titleFont;
    private Font menuFont;
    private Font scoreFont;
    private Font winnerFont;
    private Stroke centreStroke;

    private boolean finished;

    private int timer;
    private long timeThen, timeNow, timeLate;

    private enum GameState { MENU, INGAME, POINTSCORED, WINNER };
    private GameState state;

    private float playerLY, playerRY;
    private float ballX, ballY, ballVX, ballVY;
    private int playerLScore, playerRScore;
    private boolean singlePlayer;

    /**
     * Creates and runs a new game of Pong.
     */
    public Pong() {
        init();
        run();
        quit();
    }

    /**
     * Initializes the game state and display.
     */
    private void init() {
        // setup game state
        titleFont = new Font("Verdana", Font.BOLD, 60);
        menuFont = new Font("Verdana", Font.BOLD, 10);
        scoreFont = new Font("Fixed Width", Font.BOLD, 80);
        winnerFont = new Font("Verdana", Font.BOLD, 18);
        centreStroke = new BasicStroke(BALL_RADIUS, BasicStroke.CAP_BUTT,
                                       BasicStroke.JOIN_MITER, 10f, new float[]{8f}, 0f);
        keys = new boolean[256];
        singlePlayer = false;
        state = GameState.MENU;
        resetPoint();

        // setup the game window
        window = new Frame(GAME_NAME);
        window.setIgnoreRepaint(true);
        window.setUndecorated(true);
        window.setSize(GAME_WIDTH, GAME_HEIGHT);
        window.setResizable(false);
        window.setLocationRelativeTo(null);
        window.addWindowListener(new WindowAdapter(){
            public void windowClosing(WindowEvent evt){ finished = true; }
        });
        window.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) { keys[e.getKeyCode()] = true; }
            public void keyReleased(KeyEvent e) { keys[e.getKeyCode()] = false; }
        });

        // show the window
        window.setVisible(true);
        window.requestFocus();

        // setup double buffering on the display
        window.createBufferStrategy(2);
        bufStrat = window.getBufferStrategy();
    }

    /**
     * Runs the game, executing game logic and rendering the current state.
     */
    private void run() {
        while(!finished) {
            logic();
            render();
            sync();
        }
    }

    /**
     * Cleans up any resources and exits the program as soon as possible.
     */
    private void quit() {
        window.dispose();
    }

    /**
     * Updates the game state for a frame.
     */
    private void logic() {
        if (keys[KeyEvent.VK_ESCAPE]) {
            finished = true;
            return;
        }

        switch(state) {
            case MENU:
                updateMenu(); break;
            case INGAME:
                updateGame(); break;
            case POINTSCORED:
                updatePointScored(); break;
            case WINNER:
                updateWinner(); break;
        }
    }

    private void updateMenu() {
        if (keys[KeyEvent.VK_1]) { // start single player game
            singlePlayer = true;
            resetGame();
            state = GameState.INGAME;
        } else if (keys[KeyEvent.VK_2]) { // start two player game
            singlePlayer = false;
            resetGame();
            state = GameState.INGAME;
        }
    }

    private void updateGame() {
        // calculate new position for player one
        if (keys[KeyEvent.VK_A] && playerLY > 20) {
            playerLY -= PADDLE_SPEED;
        }
        if (keys[KeyEvent.VK_Z] && playerLY + PADDLE_HEIGHT < GAME_HEIGHT - 20) {
            playerLY += PADDLE_SPEED;
        }

        // calculate new position for player two 
        if (!singlePlayer) {
            if (keys[KeyEvent.VK_UP] && playerRY > 20) {
                playerRY -= PADDLE_SPEED;
            }
            if (keys[KeyEvent.VK_DOWN] && playerRY + PADDLE_HEIGHT < GAME_HEIGHT - 20) {
                playerRY += PADDLE_SPEED;
            }
        } else {
            updateAI();
        }

        // do collision detection
        updateBallCollision();        

        // calculate new position for the ball
        ballX += ballVX;
        ballY += ballVY;
    }

    private void updatePointScored() {
        timer++;
        if (timer >= INTERPOINT_DELAY) {
            timer = 0;

            if (playerLScore >= SCORE_TO_WIN || playerRScore >= SCORE_TO_WIN) {
                // one player has one after the last point
                state = GameState.WINNER;
            } else {
                // need to keep playing to find a winner
                resetPoint();
                state = GameState.INGAME;
            }
        }
    }

    private void updateWinner() {
        timer++;
        if (timer >= INTERGAME_DELAY) {
            timer = 0;
            state = GameState.MENU;
        }
    }

    /**
     * Renders the current state of the game.
     */
    private void render() {
        Graphics2D g = (Graphics2D)bufStrat.getDrawGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        switch(state) {
            case MENU:
                renderMenu(g); break;
            case INGAME:
            case POINTSCORED:
                renderGame(g); break;
            case WINNER:
                renderWinner(g); break;
        }

        g.dispose();
        bufStrat.show();
    }

    private void renderMenu(Graphics2D g) {
        // clear the screen
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);

        // draw the title
        g.setFont(titleFont);
        g.setColor(Color.WHITE);
        g.fillRect(0, 70, GAME_WIDTH, 5);
        g.drawString(GAME_NAME, 55, 130);
        g.fillRect(0, 140, GAME_WIDTH, 5);

        // draw the instruction text
        g.setFont(menuFont);
        g.drawString("(1) player - (2) players - (Esc)ape", 70, GAME_HEIGHT - 10);
    }

    private void renderGame(Graphics2D g) {
        // clear the screen
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);

        // draw the scores
        g.setFont(scoreFont);
        g.setColor(Color.DARK_GRAY);
        g.drawString(String.valueOf(playerLScore), 120, 70);
        g.drawString(String.valueOf(playerRScore), 155, 70);

        // draw the top and bottom edges
        g.setColor(Color.WHITE);
        g.fillRect(GFX_SPACER, GAME_HEIGHT - 2 * GFX_SPACER, GAME_WIDTH - 2 * GFX_SPACER, GFX_SPACER);
        g.fillRect(GFX_SPACER, GFX_SPACER, GAME_WIDTH - 2 * GFX_SPACER, GFX_SPACER);

        // draw the centre line
        g.setStroke(centreStroke);
        g.drawLine(GAME_WIDTH / 2, 2 * GFX_SPACER, GAME_WIDTH / 2, GAME_HEIGHT - 2 * GFX_SPACER);

        // draw the two paddles
        g.setColor(Color.WHITE);
        g.fillRect(GFX_SPACER, (int)playerLY, GFX_SPACER, PADDLE_HEIGHT);
        g.fillRect(GAME_WIDTH - 2 * GFX_SPACER, (int)playerRY, PADDLE_HALFWIDTH * 2, PADDLE_HEIGHT);

        // draw the ball
        g.setColor(Color.WHITE);
        g.fillRect((int)(ballX - BALL_RADIUS), (int)(ballY - BALL_RADIUS),
                   (int)(BALL_RADIUS * 2), (int)(BALL_RADIUS * 2));
    }

    private void renderWinner(Graphics2D g) {
        // render the game in the background
        renderGame(g);
        Color maskBack = new Color(100, 100, 100, 128);
        g.setColor(maskBack);
        g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);

        // draw the winner string
        String winner = (playerLScore > playerRScore)
                            ? "Left player wins!"
                            : "Right player wins!";

        g.setFont(winnerFont);
        g.setColor(Color.WHITE);
        g.drawString(winner, 85, 120);
    }

    /**
     * Resets the position of the ball and paddles for a new point.
     */
    private void resetPoint() {
        playerLY = GAME_HEIGHT / 2 - PADDLE_HEIGHT / 2;
        playerRY = GAME_HEIGHT / 2 - PADDLE_HEIGHT / 2;
        ballX = GAME_WIDTH / 2;
        ballY = GAME_HEIGHT / 2;
        ballVX = (Math.random() > 0.5) ? 2 : -2;
        ballVY = (Math.random() > 0.5) ? 2 : -2;
    }

    /**
     * Resets the game state for a new game.
     */
    private void resetGame() {
        playerLScore = 0;
        playerRScore = 0;
        resetPoint();
    }

    /**
     * Checks for collision of the ball again the walls and player paddles. If a
     * player has scored this method will change the state appropriately.
     */
    private void updateBallCollision() {
        // check for collision against the top and bottom
        if ((ballY - BALL_RADIUS <= 2 * GFX_SPACER) ||
            (ballY + BALL_RADIUS >= GAME_HEIGHT - 2 * GFX_SPACER)) {
            ballVY = -ballVY;
        }

        // check for collision with paddles
        final int PADDLE_HALFHEIGHT = PADDLE_HEIGHT / 2; 

        // calculate the penetration on each axis
        float penRX = PADDLE_HALFWIDTH + BALL_RADIUS - Math.abs(ballX - (GAME_WIDTH - 15));
        float penRY = PADDLE_HALFHEIGHT + BALL_RADIUS - Math.abs(ballY - (playerRY + PADDLE_HALFHEIGHT));
        float penLX = PADDLE_HALFWIDTH + BALL_RADIUS - Math.abs(ballX - 15);
        float penLY = PADDLE_HALFHEIGHT + BALL_RADIUS - Math.abs(ballY - (playerLY + PADDLE_HALFHEIGHT));

        if (penRX > 0 && penRY > 0) { // hit right paddle
            ballVX = -ballVX;
            if (penRX < penRY) {
                ballX -= penRX;
            } else {
                ballY += (ballY > playerRY) ? penRY : -penRY;
                ballVY = -ballVY;
            }
        } else if (penLX > 0 && penLY > 0) { // hit left paddle
            ballVX = -ballVX;
            if (penLX < penLY) {
                ballX += penLX;
            } else {
                ballY += (ballY > playerLY) ? penLY : -penLY;
                ballVY = -ballVY;
            }
        }

        // increase the speed of the ball with every hit
        if ((penRX > 0 && penRY > 0) || (penLX > 0 && penLY > 0)) {
            ballVX *= BALL_SPEED_INCREASE;
            ballVY *= BALL_SPEED_INCREASE;
        }

        // check for points scored
        if (ballX < 0) {
            playerRScore++;
            state = GameState.POINTSCORED;
        } else if (ballX > GAME_WIDTH) {
            playerLScore++;
            state = GameState.POINTSCORED;
        }
    }

    /**
     * Runs the artificial stupidity calculations for this frame.
     */
    private void updateAI() {
        float paddleDest;

        if (ballVX > 0) {
            // ball is moving toward AI, move paddle to ball
            paddleDest = ballY - PADDLE_HEIGHT / 2;
        } else {
            // ball is moving away, move paddle back to centre
            paddleDest = GAME_HEIGHT / 2 - PADDLE_HEIGHT / 2;
        }

        if (playerRY > paddleDest && playerRY > 20) {
            playerRY -= PADDLE_SPEED;
        } else if (playerRY < paddleDest && playerRY + PADDLE_HEIGHT < GAME_HEIGHT - 20) {
            playerRY += PADDLE_SPEED;
        }
    }

    /**
     * Synchrnoizes the display to the desired frame rate.
     */
    private void sync() {
        long timeOfNextFrame = (1000000000l / GAME_FPS) + timeThen;
        timeNow = System.nanoTime();

        while(timeOfNextFrame > timeNow + timeLate) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) { }
            timeNow = System.nanoTime();
        }

        if (timeNow > timeOfNextFrame) {
            timeLate = timeNow - timeOfNextFrame;
        } else {
            timeLate = 0;
        }

        timeThen = timeNow;
    }

    /**
     * Entry point to the application.
     */
    public static void main(String[] args) {
        Pong p = new Pong();
    }
}
/*
*版权所有(c)2007 Eric Woroshow
*
*特此免费向任何人授予许可
*获取本软件和相关文档的副本
*文件(以下简称“软件”),用于处理本软件而无需
*限制,包括但不限于使用权,
*复制、修改、合并、发布、分发、再许可和/或出售
*软件的副本,并允许
*为此提供的软件符合以下要求:
*条件:
* 
*上述版权声明和本许可声明应
*包含在软件的所有副本或重要部分中。
* 
*软件按“原样”提供,无任何形式的担保,
*明示或暗示,包括但不限于保证
*适销性、特定用途的适用性和
*不干涉。在任何情况下,作者或版权
*持有人应承担任何索赔、损害赔偿或其他责任,
*无论是在合同诉讼、侵权诉讼还是其他诉讼中
*来自、来自或与软件或使用有关;或
*软件中的其他交易。
*/
包ca.ericw.pong;
导入java.awt.BasicStroke;
导入java.awt.Color;
导入java.awt.Font;
导入java.awt.Frame;
导入java.awt.Graphics2D;
导入java.awt.RenderingHints;
导入java.awt.Stroke;
导入java.awt.event.KeyAdapter;
导入java.awt.event.KeyEvent;
导入java.awt.event.WindowAdapter;
导入java.awt.event.WindowEvent;
导入java.awt.image.BufferStrategy;
公开课乒乓球{
公共静态最终字符串游戏_NAME=“PONG!”;
私有静态最终整数游戏宽度=320;
私人静态最终整数游戏高度=240;
私人静态最终整数游戏_FPS=60;
专用静态最终内桨高度=60;
专用静态最终int桨_半宽=5;
专用静态最终浮桨_速度=2f;
私人静态最终浮球_半径=5;
私人静态最终浮球\速度\增加=1.05f;
专用静态最终int GFX_垫片=10;
私人静态最终整数分数为5分;
私有静态最终int INTERPOINT_DELAY=游戏FPS;
私有静态最终整数整数整数整数延迟=3*GAME\u FPS;
专用框架窗口;
私人缓冲策略bufStrat;
私有布尔[]密钥;
私人字体标题;
私有字体菜单;
专用字体;
私有字体winnerFont;
私人中风中心;
私有布尔完成;
专用整数定时器;
私人长时间,时间现在,时间;
私有枚举游戏状态{MENU,INGAME,POINTSCORED,WINNER};
私人游戏状态;
私家花车嬉戏,嬉戏;
私人浮动球X、球Y、球VX、球VY;
私人int playerScore,playerScore;
私有布尔单玩家;
/**
*创建并运行一个新的乒乓球游戏。
*/
公共乒乓球{
init();
run();
退出();
}
/**
*初始化游戏状态和显示。
*/
私有void init(){
//设置游戏状态
titleFont=新字体(“Verdana”,Font.BOLD,60);
menuFont=新字体(“Verdana”,Font.BOLD,10);
scoreFont=新字体(“固定宽度”,Font.BOLD,80);
winnerFont=新字体(“Verdana”,Font.BOLD,18);
中心行程=新的基本行程(球半径、基本行程、端盖、对接、,
BasicStroke.JOIN_-MITER,10f,新float[]{8f},0f);
键=新布尔值[256];
单人游戏=假;
state=GameState.MENU;
重置点();
//设置游戏窗口
窗口=新帧(游戏名称);
window.setIgnoreRepaint(true);
窗口。设置未装饰(真实);
设置大小(游戏宽度、游戏高度);
window.setresizeable(false);
window.setLocationRelativeTo(空);
addWindowListener(新的WindowAdapter(){
public void windowClosing(WindowEvent evt){finished=true;}
});
addKeyListener(新的KeyAdapter(){
public void keyPressed(KeyEvent e){keys[e.getKeyCode()]=true;}
public void keyereleased(KeyEvent e){keys[e.getKeyCode()]=false;}
});
//显示窗口
window.setVisible(true);
requestFocus();
//在显示器上设置双缓冲
窗口策略(2);
bufStrat=window.getBufferStrategy();
}
/**
*运行游戏,执行游戏逻辑并呈现当前状态。
*/
私家车{
当(!完成){
逻辑()
/*
 * Copyright (c) 2007 Eric Woroshow
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package ca.ericw.pong;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;

public class Pong {

    public static final String GAME_NAME = "PONG!";
    private static final int GAME_WIDTH = 320;
    private static final int GAME_HEIGHT = 240;
    private static final int GAME_FPS = 60;
    private static final int PADDLE_HEIGHT = 60;
    private static final int PADDLE_HALFWIDTH = 5;
    private static final float PADDLE_SPEED = 2f;
    private static final float BALL_RADIUS = 5;
    private static final float BALL_SPEED_INCREASE = 1.05f;
    private static final int GFX_SPACER = 10;
    private static final int SCORE_TO_WIN = 5;
    private static final int INTERPOINT_DELAY = GAME_FPS;
    private static final int INTERGAME_DELAY = 3 * GAME_FPS;

    private Frame window;
    private BufferStrategy bufStrat;
    private boolean[] keys;

    private Font titleFont;
    private Font menuFont;
    private Font scoreFont;
    private Font winnerFont;
    private Stroke centreStroke;

    private boolean finished;

    private int timer;
    private long timeThen, timeNow, timeLate;

    private enum GameState { MENU, INGAME, POINTSCORED, WINNER };
    private GameState state;

    private float playerLY, playerRY;
    private float ballX, ballY, ballVX, ballVY;
    private int playerLScore, playerRScore;
    private boolean singlePlayer;

    /**
     * Creates and runs a new game of Pong.
     */
    public Pong() {
        init();
        run();
        quit();
    }

    /**
     * Initializes the game state and display.
     */
    private void init() {
        // setup game state
        titleFont = new Font("Verdana", Font.BOLD, 60);
        menuFont = new Font("Verdana", Font.BOLD, 10);
        scoreFont = new Font("Fixed Width", Font.BOLD, 80);
        winnerFont = new Font("Verdana", Font.BOLD, 18);
        centreStroke = new BasicStroke(BALL_RADIUS, BasicStroke.CAP_BUTT,
                                       BasicStroke.JOIN_MITER, 10f, new float[]{8f}, 0f);
        keys = new boolean[256];
        singlePlayer = false;
        state = GameState.MENU;
        resetPoint();

        // setup the game window
        window = new Frame(GAME_NAME);
        window.setIgnoreRepaint(true);
        window.setUndecorated(true);
        window.setSize(GAME_WIDTH, GAME_HEIGHT);
        window.setResizable(false);
        window.setLocationRelativeTo(null);
        window.addWindowListener(new WindowAdapter(){
            public void windowClosing(WindowEvent evt){ finished = true; }
        });
        window.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) { keys[e.getKeyCode()] = true; }
            public void keyReleased(KeyEvent e) { keys[e.getKeyCode()] = false; }
        });

        // show the window
        window.setVisible(true);
        window.requestFocus();

        // setup double buffering on the display
        window.createBufferStrategy(2);
        bufStrat = window.getBufferStrategy();
    }

    /**
     * Runs the game, executing game logic and rendering the current state.
     */
    private void run() {
        while(!finished) {
            logic();
            render();
            sync();
        }
    }

    /**
     * Cleans up any resources and exits the program as soon as possible.
     */
    private void quit() {
        window.dispose();
    }

    /**
     * Updates the game state for a frame.
     */
    private void logic() {
        if (keys[KeyEvent.VK_ESCAPE]) {
            finished = true;
            return;
        }

        switch(state) {
            case MENU:
                updateMenu(); break;
            case INGAME:
                updateGame(); break;
            case POINTSCORED:
                updatePointScored(); break;
            case WINNER:
                updateWinner(); break;
        }
    }

    private void updateMenu() {
        if (keys[KeyEvent.VK_1]) { // start single player game
            singlePlayer = true;
            resetGame();
            state = GameState.INGAME;
        } else if (keys[KeyEvent.VK_2]) { // start two player game
            singlePlayer = false;
            resetGame();
            state = GameState.INGAME;
        }
    }

    private void updateGame() {
        // calculate new position for player one
        if (keys[KeyEvent.VK_A] && playerLY > 20) {
            playerLY -= PADDLE_SPEED;
        }
        if (keys[KeyEvent.VK_Z] && playerLY + PADDLE_HEIGHT < GAME_HEIGHT - 20) {
            playerLY += PADDLE_SPEED;
        }

        // calculate new position for player two 
        if (!singlePlayer) {
            if (keys[KeyEvent.VK_UP] && playerRY > 20) {
                playerRY -= PADDLE_SPEED;
            }
            if (keys[KeyEvent.VK_DOWN] && playerRY + PADDLE_HEIGHT < GAME_HEIGHT - 20) {
                playerRY += PADDLE_SPEED;
            }
        } else {
            updateAI();
        }

        // do collision detection
        updateBallCollision();        

        // calculate new position for the ball
        ballX += ballVX;
        ballY += ballVY;
    }

    private void updatePointScored() {
        timer++;
        if (timer >= INTERPOINT_DELAY) {
            timer = 0;

            if (playerLScore >= SCORE_TO_WIN || playerRScore >= SCORE_TO_WIN) {
                // one player has one after the last point
                state = GameState.WINNER;
            } else {
                // need to keep playing to find a winner
                resetPoint();
                state = GameState.INGAME;
            }
        }
    }

    private void updateWinner() {
        timer++;
        if (timer >= INTERGAME_DELAY) {
            timer = 0;
            state = GameState.MENU;
        }
    }

    /**
     * Renders the current state of the game.
     */
    private void render() {
        Graphics2D g = (Graphics2D)bufStrat.getDrawGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        switch(state) {
            case MENU:
                renderMenu(g); break;
            case INGAME:
            case POINTSCORED:
                renderGame(g); break;
            case WINNER:
                renderWinner(g); break;
        }

        g.dispose();
        bufStrat.show();
    }

    private void renderMenu(Graphics2D g) {
        // clear the screen
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);

        // draw the title
        g.setFont(titleFont);
        g.setColor(Color.WHITE);
        g.fillRect(0, 70, GAME_WIDTH, 5);
        g.drawString(GAME_NAME, 55, 130);
        g.fillRect(0, 140, GAME_WIDTH, 5);

        // draw the instruction text
        g.setFont(menuFont);
        g.drawString("(1) player - (2) players - (Esc)ape", 70, GAME_HEIGHT - 10);
    }

    private void renderGame(Graphics2D g) {
        // clear the screen
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);

        // draw the scores
        g.setFont(scoreFont);
        g.setColor(Color.DARK_GRAY);
        g.drawString(String.valueOf(playerLScore), 120, 70);
        g.drawString(String.valueOf(playerRScore), 155, 70);

        // draw the top and bottom edges
        g.setColor(Color.WHITE);
        g.fillRect(GFX_SPACER, GAME_HEIGHT - 2 * GFX_SPACER, GAME_WIDTH - 2 * GFX_SPACER, GFX_SPACER);
        g.fillRect(GFX_SPACER, GFX_SPACER, GAME_WIDTH - 2 * GFX_SPACER, GFX_SPACER);

        // draw the centre line
        g.setStroke(centreStroke);
        g.drawLine(GAME_WIDTH / 2, 2 * GFX_SPACER, GAME_WIDTH / 2, GAME_HEIGHT - 2 * GFX_SPACER);

        // draw the two paddles
        g.setColor(Color.WHITE);
        g.fillRect(GFX_SPACER, (int)playerLY, GFX_SPACER, PADDLE_HEIGHT);
        g.fillRect(GAME_WIDTH - 2 * GFX_SPACER, (int)playerRY, PADDLE_HALFWIDTH * 2, PADDLE_HEIGHT);

        // draw the ball
        g.setColor(Color.WHITE);
        g.fillRect((int)(ballX - BALL_RADIUS), (int)(ballY - BALL_RADIUS),
                   (int)(BALL_RADIUS * 2), (int)(BALL_RADIUS * 2));
    }

    private void renderWinner(Graphics2D g) {
        // render the game in the background
        renderGame(g);
        Color maskBack = new Color(100, 100, 100, 128);
        g.setColor(maskBack);
        g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);

        // draw the winner string
        String winner = (playerLScore > playerRScore)
                            ? "Left player wins!"
                            : "Right player wins!";

        g.setFont(winnerFont);
        g.setColor(Color.WHITE);
        g.drawString(winner, 85, 120);
    }

    /**
     * Resets the position of the ball and paddles for a new point.
     */
    private void resetPoint() {
        playerLY = GAME_HEIGHT / 2 - PADDLE_HEIGHT / 2;
        playerRY = GAME_HEIGHT / 2 - PADDLE_HEIGHT / 2;
        ballX = GAME_WIDTH / 2;
        ballY = GAME_HEIGHT / 2;
        ballVX = (Math.random() > 0.5) ? 2 : -2;
        ballVY = (Math.random() > 0.5) ? 2 : -2;
    }

    /**
     * Resets the game state for a new game.
     */
    private void resetGame() {
        playerLScore = 0;
        playerRScore = 0;
        resetPoint();
    }

    /**
     * Checks for collision of the ball again the walls and player paddles. If a
     * player has scored this method will change the state appropriately.
     */
    private void updateBallCollision() {
        // check for collision against the top and bottom
        if ((ballY - BALL_RADIUS <= 2 * GFX_SPACER) ||
            (ballY + BALL_RADIUS >= GAME_HEIGHT - 2 * GFX_SPACER)) {
            ballVY = -ballVY;
        }

        // check for collision with paddles
        final int PADDLE_HALFHEIGHT = PADDLE_HEIGHT / 2; 

        // calculate the penetration on each axis
        float penRX = PADDLE_HALFWIDTH + BALL_RADIUS - Math.abs(ballX - (GAME_WIDTH - 15));
        float penRY = PADDLE_HALFHEIGHT + BALL_RADIUS - Math.abs(ballY - (playerRY + PADDLE_HALFHEIGHT));
        float penLX = PADDLE_HALFWIDTH + BALL_RADIUS - Math.abs(ballX - 15);
        float penLY = PADDLE_HALFHEIGHT + BALL_RADIUS - Math.abs(ballY - (playerLY + PADDLE_HALFHEIGHT));

        if (penRX > 0 && penRY > 0) { // hit right paddle
            ballVX = -ballVX;
            if (penRX < penRY) {
                ballX -= penRX;
            } else {
                ballY += (ballY > playerRY) ? penRY : -penRY;
                ballVY = -ballVY;
            }
        } else if (penLX > 0 && penLY > 0) { // hit left paddle
            ballVX = -ballVX;
            if (penLX < penLY) {
                ballX += penLX;
            } else {
                ballY += (ballY > playerLY) ? penLY : -penLY;
                ballVY = -ballVY;
            }
        }

        // increase the speed of the ball with every hit
        if ((penRX > 0 && penRY > 0) || (penLX > 0 && penLY > 0)) {
            ballVX *= BALL_SPEED_INCREASE;
            ballVY *= BALL_SPEED_INCREASE;
        }

        // check for points scored
        if (ballX < 0) {
            playerRScore++;
            state = GameState.POINTSCORED;
        } else if (ballX > GAME_WIDTH) {
            playerLScore++;
            state = GameState.POINTSCORED;
        }
    }

    /**
     * Runs the artificial stupidity calculations for this frame.
     */
    private void updateAI() {
        float paddleDest;

        if (ballVX > 0) {
            // ball is moving toward AI, move paddle to ball
            paddleDest = ballY - PADDLE_HEIGHT / 2;
        } else {
            // ball is moving away, move paddle back to centre
            paddleDest = GAME_HEIGHT / 2 - PADDLE_HEIGHT / 2;
        }

        if (playerRY > paddleDest && playerRY > 20) {
            playerRY -= PADDLE_SPEED;
        } else if (playerRY < paddleDest && playerRY + PADDLE_HEIGHT < GAME_HEIGHT - 20) {
            playerRY += PADDLE_SPEED;
        }
    }

    /**
     * Synchrnoizes the display to the desired frame rate.
     */
    private void sync() {
        long timeOfNextFrame = (1000000000l / GAME_FPS) + timeThen;
        timeNow = System.nanoTime();

        while(timeOfNextFrame > timeNow + timeLate) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) { }
            timeNow = System.nanoTime();
        }

        if (timeNow > timeOfNextFrame) {
            timeLate = timeNow - timeOfNextFrame;
        } else {
            timeLate = 0;
        }

        timeThen = timeNow;
    }

    /**
     * Entry point to the application.
     */
    public static void main(String[] args) {
        Pong p = new Pong();
    }
}