for ActionScript3
PongPod - Source code

PongPod.as

/**
 * PongPod.<br>
 * Play a pong with a wheel.<br><br>
 *
 * Copyright (C) 2005 Kenta Cho. Some rights reserved.
 * 
 * - 画面下の灰色の丸(ホイール)にカーソルを合わせてください
 * - クリックするとゲームが始まります
 * - ホイールに沿って丸くカーソルを動かすとラケットが動きます
 * - ラケットでボールをはじき返してください
 * - ボールが中央の黒丸に入るとミスです
 * - ミス3つでゲームオーバー
 */
package {
  import flash.display.Sprite;
  import flash.display.TextField;
  import flash.display.TextFieldAutoSize;
  import flash.text.TextFormat;
  import flash.media.Sound;
  import flash.net.URLRequest;
  import flash.events.Event;
  import flash.events.EventType;
  import flash.events.MouseEventType;

  // Use '-default-size 340 550 -default-frame-rate 30'
  //  as the additional compiler arguments.
  const WIDTH:int = 340;
  const HEIGHT:int = 550;
  const FRAMERATE:int = 30;
  const PI:Number = 3.1415;
  const HALF_PI:Number = PI / 2;
  const TWO_PI:Number = PI * 2;

  var field:Field;
  var wheel:Wheel;
  var racket:Racket;
  var ball:Array;
  const BALL_NUM:int = 25;
  const TITLE:int = 0;
  const IN_GAME:int = 1;
  const GAMEOVER:int = 2;
  var state:int;
  var title:Title;
  var gameover:Gameover;
  var score:int;
  var se:Se;
  var mousePressed:Boolean;

  public class PongPod extends Sprite {
    public function PongPod() {
      graphics.beginFill(0xf8f8f8);
      graphics.drawRect(0, 0, WIDTH, HEIGHT);
      graphics.endFill();
      field = new Field;
      field.init(this);
      addChild(field);
      wheel = new Wheel;
      wheel.init();
      addChild(wheel);
      racket = new Racket;
      racket.init();
      addChild(racket);
      title = new Title;
      title.init(this);
      addChild(title.titleText);
      gameover = new Gameover;
      gameover.init(this);
      addChild(gameover.gameoverText);
      se = new Se;
      se.load();
      Util.init();
      addChild(Util.scoreText);
      addEventListener(EventType.ENTER_FRAME, onEnterFrame);
      mousePressed = false;
      addEventListener(MouseEventType.MOUSE_DOWN, onMouseDown);
      addEventListener(MouseEventType.MOUSE_UP, onMouseUp);
      startTitle();
    }

    public function startTitle() {
      state = TITLE;
      Util.scoreText.visible = false;
      racket.visible = false;
      title.start();
    }

    public function startInGame() {
      state = IN_GAME;
      score = 0;
      Util.updateScore();
      Util.scoreText.visible = true;
      racket.visible = true;
      se.init();
      BallPool.initBalls();
      field.start();
      racket.start();
    }

    public function startGameover() {
      state = GAMEOVER;
      racket.visible = false;
      BallPool.initBalls();
      gameover.start();
    }

    public function onEnterFrame(evt:Event) {
      switch (state) {
      case TITLE:
        field.draw();
        wheel.move();
        title.draw();
        break;
      case IN_GAME:
        field.draw();
        field.move();
        wheel.move();
        BallPool.moveBalls();
        racket.move();
        racket.draw();
        BallPool.drawBalls();
        se.playAllSes();
        break;
      case GAMEOVER:
        field.draw();
        wheel.move();
        gameover.draw();
        break;
      }
    }
    
    public function onMouseDown(evt:Event) {
      mousePressed = true;
    }

    public function onMouseUp(evt:Event) {
      mousePressed = false;
    }

    public function getMousePressed():Boolean {
      return mousePressed;
    }
  }

  private class Util {
    public static var scoreText:TextField;
    
    public static function init() {
      scoreText = new TextField;
      scoreText.x = 230;
      scoreText.y = 10;
      scoreText.autoSize = TextFieldAutoSize.RIGHT;
      var format:TextFormat = new TextFormat;
      format.font = "Verdana";
      format.color = 0xffffff;
      format.size = 20;
      scoreText.defaultTextFormat = format;
    }

    public static function updateScore() {
      scoreText.text = String(score);
    }

    public static function adjustDeg(d:Number):Number {
      if (d > PI)
        return d - PI * 2;
      else if (d < -PI)
        return d + PI * 2;
      else
        return d;
    }
    
    public static function dist
     (x1:Number, y1:Number, x2:Number, y2:Number):Number {
      return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    }
  }

  /**
   * 2D vector.
   */
  private class Vector {
    public var x:Number;
    public var y:Number;
  }

  /**
   * My racket.
   */
  private class Racket extends Sprite {
    const SENSITIVITY:Number = 0.7;
    var radius:Number;
    var deg:Number;
    var pos:Array;
    var p1:Vector;
    var p2:Vector;
    var rWidth:Number;
    var rHeight:Number;

    public function init() {
      radius = field.bwRadius * 1.5;
      pos = new Array;
      for (var i:int = 0; i < 2; i++)
        pos[i] = new Vector;
      p1 = new Vector;
      p2 = new Vector;
      rWidth = 70;
      rHeight = 7;
      x = field.pos.x + field.centerPos.x;
      y = field.pos.y + field.centerPos.y;
      graphics.beginFill(0x000000);
      graphics.drawRect(-rWidth / 2, -radius - rHeight / 2, rWidth, rHeight);
      graphics.drawRect(-rWidth / 2, radius - rHeight / 2, rWidth, rHeight);
      graphics.endFill();
    }

    public function start() {
      deg = 0;
    }

    public function move() {
      deg -= wheel.angVel * SENSITIVITY;
      deg = Util.adjustDeg(deg);
      var d:Number = deg;
      for (var i:int = 0; i < pos.length; i++) {
        pos[i].x = field.centerPos.x - Math.sin(d) * radius;
        pos[i].y = field.centerPos.y + Math.cos(d) * radius;
        p1.x = pos[i].x - Math.sin(d + HALF_PI) * rWidth / 2;
        p1.y = pos[i].y + Math.cos(d + HALF_PI) * rWidth / 2;
        p2.x = pos[i].x - Math.sin(d - HALF_PI) * rWidth / 2;
        p2.y = pos[i].y + Math.cos(d - HALF_PI) * rWidth / 2;
        for (var j:int = 0; j < ball.length; j++)
          if (ball[j].isExist)
            if (ball[j].checkHit(p1, p2))
              ball[j].reflectWithRacket(d + HALF_PI);
        d += PI;
      }
    }

    public function draw() {
      rotation = deg * 180 / PI;
    }
  }

  /**
   * Balls.
   */
  private class Ball extends Sprite {
    var pos:Vector;
    var ppos:Vector;
    var deg:Number;
    var speed:Number;
    var size:Number;
    var trgSize:Number;
    var isExist:Boolean;
    var reflectCnt:int;
    var idx:int;

    public function Ball() {
      pos = new Vector;
      ppos = new Vector;
      graphics.beginFill(0xffffff);
      graphics.drawCircle(0, 0, 1);
      graphics.endFill();
    }
 
    public function set(x:Number, y:Number, d:Number, sp:Number, sz:Number) {
      pos.x = x;
      pos.y = y;
      deg = Util.adjustDeg(d);
      speed = sp;
      trgSize = sz;
      size = 0.1;
      reflectCnt = 0;
      draw();
      isExist = true;
      visible = true;
    }

    public function move() {
      ppos.x = pos.x;
      ppos.y = pos.y;
      pos.x -= Math.sin(deg) * speed;
      pos.y += Math.cos(deg) * speed;
      size += (trgSize - size) * 0.1;
      if (pos.x < 0 || pos.x >= field.size.x)
        reflect(0);
      if (pos.y < 0 || pos.y >= field.size.y)
        reflect(HALF_PI);
      if (reflectCnt > 0)
        reflectCnt--;
      if (Util.dist(pos.x, pos.y, field.centerPos.x, field.centerPos.y)
        < field.bwRadius * 0.75 + size) {
        field.incOutCnt();
        isExist = false;
        visible = false;
        return;
      }
    }

    private function reflect(d:Number) {
      var aod1:Number = Math.abs(Util.adjustDeg(deg - d));
      var aod2:Number = Math.abs(Util.adjustDeg(deg - (d + PI)));
      var od:Number;
      var aod:Number;
      if (aod1 < aod2) {
        od = Util.adjustDeg(deg - d);
        aod = aod1;
      } else {
        od = Util.adjustDeg(deg - (d + PI));
        aod = aod2;
      }
      if (od < 0)
        deg += aod * 2;
      else
        deg -= aod * 2;
      pos.x = ppos.x;
      pos.y = ppos.y;
      pos.x -= Math.sin(deg) * speed;
      pos.y += Math.cos(deg) * speed;
      se.playSe(idx % 7);
    }

    public function reflectWithRacket(d:Number) {
      if (reflectCnt <= 0) {
        reflect(d);
        se.playSe(7);
        score++;
        Util.updateScore();
      }
      reflectCnt = 3;
    }

    public function checkHit(p:Vector, pp:Vector):Boolean  {
      var bmvx:Number;
      var bmvy:Number;
      var inaa:Number;
      bmvx = pp.x;
      bmvy = pp.y;
      bmvx -= p.x;
      bmvy -= p.y;
      inaa = bmvx * bmvx + bmvy * bmvy;
      if (inaa > 0.00001) {
        var sofsx:Number;
        var sofsy:Number;
        var inab:Number;
        var hd:Number;
        sofsx = pos.x;
        sofsy = pos.y;
        sofsx -= p.x;
        sofsy -= p.y;
        inab = bmvx * sofsx + bmvy * sofsy;
        if (inab >= 0 && inab <= inaa) {
          hd = sofsx * sofsx + sofsy * sofsy - inab * inab / inaa;
          if (hd >= 0 && hd <= size * 0.7 + 7.0) {
            return true;
          }
        }
      }
      return false;
    }
    
    public function draw() {
      x = pos.x + field.pos.x;
      y = pos.y + field.pos.y;
      scaleX = scaleY = size;
    }
  }

  private class BallPool {
    static var ballIdx:int = 0;

    public static function initBalls() {
      for (var i:int = 0; i < ball.length; i++) {
        ball[i].idx = i;
        ball[i].isExist = false;
        ball[i].visible = false;
      }
      ballIdx = 0;
    }

    public static function moveBalls() {
      for (var i:int = 0; i < ball.length; i++)
        if (ball[i].isExist)
          ball[i].move();
    }

    public static function drawBalls() {
      for (var i:int = 0; i < ball.length; i++)
        if (ball[i].isExist)
          ball[i].draw();
    }

    public static function getBallInstance():Ball {
      for (var i:int = 0; i < ball.length; i++) {
        if (!ball[ballIdx].isExist)
          return ball[ballIdx];
        ballIdx++;
        if (ballIdx >= ball.length)
          ballIdx = 0;
      }
      return null;
    }
  }
  
  /**
   * Game field (main screen).
   */
  private class Field extends Sprite {
    var pongPod:PongPod;
    var blackhole:Blackhole;
    var outCountBall:Array;
    var pos:Vector;
    var size:Vector;
    var bwRadius:Number;
    var centerPos:Vector;
    var bwSize:Number;
    var cnt:int;
    var outCnt:int;

    public function init(pongPod:PongPod) {
      this.pongPod = pongPod;
      size = new Vector;
      size.x = 320;
      size.y = 240;
      pos = new Vector;
      pos.x = pos.y = (WIDTH - size.x) / 2;
      bwRadius = size.y * 0.1;
      centerPos = new Vector;
      centerPos.x = size.x / 2;
      centerPos.y = size.y / 2;
      changeBrightness(0);
      bwSize = 0;
      ball = new Array;
      for (var i:int = 0; i < BALL_NUM; i++) {
        ball[i] = new Ball;
        addChild(ball[i]);
      }
      blackhole = new Blackhole;
      blackhole.x = pos.x + centerPos.x;
      blackhole.y = pos.y + centerPos.y;
      draw();
      addChild(blackhole);
      outCountBall = new Array;
      for (var i:int = 0; i < 3; i++) {
        outCountBall[i] = new OutCountBall;
        outCountBall[i].x = size.x / 2 - 10 + i * 10 + pos.x;
        outCountBall[i].y = size.y / 2 + pos.y;
        addChild(outCountBall[i]);
      }
      clearOutCountBall();
    }
    
    public function start() {
      bwSize = 1;
      cnt = 0;
      outCnt = 0;
      for (var i:int = 0; i < 3; i++)
        addBall();
      clearOutCountBall();
    }
    
    private function clearOutCountBall() {
      for (var i:int = 0; i < outCountBall.length; i++)
        outCountBall[i].visible = false;
    }

    public function addBall() {
      var b:Ball = BallPool.getBallInstance();
      if (b == null)
        return;
      var x:Number;
      var y:Number;
      if (Math.random() >= 0.5) {
        x = Math.random() * (field.size.x - 40) + 20;
        if (Math.random() >= 0.5)
          y = 20;
        else
          y = field.size.y - 20;
      } else {
        y = Math.random() * (field.size.y - 40) + 20;
        if (Math.random() >= 0.5)
          x = 20;
        else
          x = field.size.x - 20;
      }
      b.set(x, y, Math.random() * TWO_PI,
            1.5 + Math.random() * 1.0, 4 + Math.random() * 3.0);
    }

    public function incOutCnt() {
      se.playSe(8);
      outCountBall[outCnt].visible = true;
      outCnt++;
      if (outCnt >= 3) {
        pongPod.startGameover();
        clearOutCountBall();
      }
    }

    public function move() {
      cnt++;
      if (cnt % 300 == 0)
        addBall();
    }
    
    public function draw() {
      blackhole.scaleX = blackhole.scaleY = bwSize;
    }

    public function changeBrightness(r:Number) {
      graphics.beginFill(Math.floor(80 * r) * 0x10000 + 
                         Math.floor(200 * r) * 0x100 + 
                         Math.floor(80 * r));
      graphics.drawRect(pos.x, pos.y, size.x, size.y);
      graphics.endFill();
    }
  }

  private class Blackhole extends Sprite {
    public function Blackhole() {
      graphics.beginFill(0x000000);
      graphics.drawCircle(0, 0, field.bwRadius);
      graphics.endFill();
    }
  }

  private class OutCountBall extends Sprite {
    public function OutCountBall() {
      graphics.beginFill(0xffffff);
      graphics.drawCircle(0, 0, 5);
      graphics.endFill();
    }
  }

  /**
   * Wheel that controls a racket.
   */
  private class Wheel extends Sprite {
    var centerPos:Vector; 
    var radFrom:Number;
    var radTo:Number;
    var cursorOn:Boolean;
    var angVel:Number;
    var pdeg:Number;

    public function init() {
      centerPos = new Vector;
      centerPos.x = WIDTH / 2;
      centerPos.y = HEIGHT * 0.75;
      radFrom = WIDTH * 0.35 / 2;
      radTo = WIDTH * 0.7 / 2;
      cursorOn = false;
      angVel = 0;
      pdeg = 0;
      x = centerPos.x;
      y = centerPos.y;
      graphics.beginFill(210 * 0x10000 + 210 * 0x100 + 210);
      graphics.drawCircle(0, 0, radTo);
      graphics.endFill();
      graphics.beginFill(0xffffff);
      graphics.drawCircle(0, 0, radFrom);
      graphics.endFill();
    }

    public function move() {
      var d:Number = Util.dist(mouseX, mouseY, 0, 0);
      if (d >= radFrom * 0.05 && d <= radTo * 1.4) {
        if (!cursorOn) {
          cursorOn = true;
          //cursor(HAND);
          pdeg = Math.atan2(mouseX, mouseY);
        }
      } else {
        if (cursorOn) {
          cursorOn = false;
          //cursor(ARROW);
        }
      }
      if (cursorOn) {
        var deg:Number = Math.atan2(mouseX, mouseY);
        angVel = Util.adjustDeg(deg - pdeg);
        pdeg = deg;
      } else {
        angVel = 0;
      }
    }
  }

  /**
   * Title screen.
   */
  private class Title {
    var pongPod:PongPod;
    var cnt:int;
    var titleText:TextField;
    
    public function init(pongPod:PongPod) {
      this.pongPod = pongPod;
      titleText = new TextField;
      var format:TextFormat = new TextFormat;
      format.font = "Verdana";
      format.color = 0xffffff;
      format.size = 35;
      titleText.defaultTextFormat = format;
      titleText.text = "PongPod";
      titleText.autoSize = TextFieldAutoSize.CENTER;
      titleText.x = field.pos.x + field.size.x / 2 - titleText.width / 2;
      titleText.y = field.pos.y + field.size.y / 2 - titleText.height / 2;
      titleText.visible = false;
    }

    public function start() {
      cnt = 0;
      field.changeBrightness(0);
      field.bwSize = 0;
    }

    public function draw() {
      cnt++;
      if (cnt > 30 && pongPod.getMousePressed() && wheel.cursorOn) {
        titleText.visible = false;
        pongPod.startInGame();
        return;
      }
      if (cnt <= 30)
        field.changeBrightness(cnt * (1.0 / 30));
      if (cnt == 45)
        titleText.visible = true;
    }
  }

  /**
   * Gameover screen.
   */
  private class Gameover {
    var pongPod:PongPod;
    var cnt:int;
    var gameoverText:TextField;
    
    public function init(pongPod:PongPod) {
      this.pongPod = pongPod;
      gameoverText = new TextField;
      var format:TextFormat = new TextFormat;
      format.font = "Verdana";
      format.color = 0xffffff;
      format.size = 27;
      gameoverText.defaultTextFormat = format;
      gameoverText.text = "GameOver";
      gameoverText.autoSize = TextFieldAutoSize.CENTER;
      gameoverText.x = field.pos.x + field.size.x / 2 - gameoverText.width / 2;
      gameoverText.y = field.pos.y + field.size.y / 2 - gameoverText.height / 2;
      gameoverText.visible = false;
    }

    public function start() {
      cnt = 0;
    }

    public function draw() {
      cnt++;
      if (cnt > 300 || (cnt > 30 && pongPod.getMousePressed() && wheel.cursorOn)) {
        gameoverText.visible = false;
        pongPod.startTitle();
        return;
      }
      if (cnt <= 30) {
        field.bwSize = 1.0 + cnt * 0.1;
        field.changeBrightness(1.0 - cnt * (1.0 / 30));
      }
      if (cnt == 45)
        gameoverText.visible = true;
    }
  }

  /**
   * Sound effect.
   */
  private class Se {
    const SE_NUM:int = 9;
    var se:Array;
    var play:Array;
    var cnt:int;
    var interval:int = 7;

    public function load() {
      se = new Array;
      play = new Array;
      for (var i:int = 0; i < SE_NUM; i++) {
        se[i] = new Sound(new URLRequest("se0" + (i + 1) + ".mp3"));
        play[i] = false;
      }
    }

    public function init() {
      for (var i:int = 0; i < SE_NUM; i++)
        play[i] = false;
      cnt = 0;
    }

    public function playSe(n:int) {
      play[n] = true;
    }

    public function playAllSes() {
      cnt++;
      var ci:int = cnt % interval;
      if (ci == 0) {
        for (var i:int = 0; i < SE_NUM; i++) {
          if (play[i]) {
            se[i].play();
            play[i] = false;
          }
        }
      }
    }
  }
}