for Proce55ing
RallX - Source code

RallX.java

/**
 * Rallx.<br>
 *  迷路脱出ゲームレーダー付き<br><br>
 *
 * Copyright 2004 Kenta Cho. All rights reserved.
 * 
 * @p5 description
 * -車をマウスで操作して、全ての旗を回収、迷路の中央の出口に向かってください。
 * -Control a car with your mouse, get all flags and go to the exit at the center of the maze.
 * -赤い岩にぶつかると車を失います。
 * -Avoid red square shaped rocks. You lose a car when you crash into a rock.
 * -マウスボタンを押しっぱなしでレーダーを見られます。迷路全体を見渡すことができますが、岩は表示されません。
 * -Hold a mouse button to see the radar screen. You can see the entire maze but rocks are not shown on the radar.
 * -画面下にある残り時間がなくなっても車を失います。
 * -You also lose a car when a time(displayed at the bottom of screen) runs out.
 */
public class RallX extends BApplet {
  /**
   * 迷路データ(0-空、1-壁、2-ゴール、3-旗、10〜-岩)
   */
  int[][] maze;
  /**
   * 迷路や出口の大きさ用定数
   */
  int MAZE_SIZE = 49, EXIT_SIZE = 7;
  /**
   * 迷路1ブロックのレーダー上の大きさ
   */
  int mazeRectSize;
  /**
   * 迷路1ブロックのスクロール画面上の大きさ
   */
  float mazeWallSize;
  /**
   * 出口の方向
   */
  int exitD;

  /**
   * スクロール画面内に表示するブロック数
   */
  int wallSightRange;
  /**
   * 自車関連データ
   */  
  float cx, cy, cd, cmd, cmx, cmy, csp, cmsp;
  /**
   * 自車のいるブロック位置
   */
  int wx, wy;
  /**
   * レーダー切り替え時カウンタ
   */
  int rcnt;
  /**
   * レーダー切り替え時アルファ値
   */
  int ca;
  /**
   * ラウンドクリア時カウンタ
   */
  int ccnt;
  /**
   * 自車クラッシュ時カウンタ
   */
  int dcnt;
  /**
   * 画面中央に表示するメッセージ
   */
  String msg;
  /**
   * メッセージ用カウンタ
   */
  int mcnt;

  /**
   * パーティクルの最大数
   */
  int PARTICLE_NUM = 32;
  /**
   * パーティクルインスタンスプール
   */
  Particle[] particle;
  /**
   * 岩の最大数
   */
  int ROCK_NUM = 50;
  /**
   * 岩インスタンスプール
   */
  Rock[] rock;
  /**
   * 残りの旗の数
   */
  int flagNum;
  /**
   * ステージ数
   */
  int stage;
  /**
   * スコア
   */
  float score;
  /**
   * 残機数
   */
  int left;
  /**
   * 残り時間
   */
  int time;
  /**
   * ゲーム状態用定数
   */
  int TITLE = 0, IN_GAME = 1;
  /**
   * ゲームの状態(タイトル/ゲーム中)
   */
  int state;
  /**
   * 各面のスタート時の残り時間(30frame * 60sec)
   */
  int STAGE_TIME = 1800;

  /**
   * フォントデータ
   */
  BFont font;
  /**
   * BGMデータ
   */
  BSound bgm;
  /**
   * SEデータ
   */
  BSound flagSe, crashSe, clearSe;

  /**
   * 初期化(フォント、サウンド読み込み、インスタンス生成)
   */
  void setup() {
    size(300, 300);
    framerate(30);
    smooth();
    cursor();
    ellipseMode(CENTER_DIAMETER);
    font = loadFont("OCR-A.vlw.gz");
    textFont(font);
    bgm = loadSound("bgm.wav");
    flagSe = loadSound("flag.wav");
    crashSe = loadSound("crash.wav");
    clearSe = loadSound("clear.wav");

    mazeRectSize = width / MAZE_SIZE;
    maze = new int[MAZE_SIZE][MAZE_SIZE];
    mazeWallSize = 40;
    wallSightRange = (int) (width / mazeWallSize) / 2 + 2;

    particle = new Particle[PARTICLE_NUM];
    for (int i = 0; i < particle.length; i++)
      particle[i] = new Particle();
    rock = new Rock[ROCK_NUM];
    for (int i = 0; i < rock.length; i++)
      rock[i] = new Rock();

    startTitle();
  }

  /**
   * タイトル初期化
   */
  void startTitle() {
    state = TITLE;
    stop(bgm);
    exitD = 0;
    stage = 0;
    left = 0;
    initStage();
    ca = 150;
    time = -999;
    float d = random(PI * 2);
    cmx = sin(d);
    cmy = cos(d);
    mcnt = 0;
  }

  /**
   * ゲーム開始
   */
  void startGame() {
    state = IN_GAME;
    exitD = 0;
    stage = -1;
    score = 0;
    left = 2;
    initStage();
    volume(bgm, 1.0f);
    jump(bgm, 0);
    repeat(bgm);
  }

  /**
   * ステージ生成
   */
  void initStage() {
    // Init a car potision.
    switch (exitD) {
      case 0 :
        cx = MAZE_SIZE * mazeWallSize / 2;
        cy = mazeWallSize * 1.5f;
        cd = PI;
        break;
      case 1 :
        cx = MAZE_SIZE * mazeWallSize - mazeWallSize * 1.5f;
        cy = MAZE_SIZE * mazeWallSize / 2;
        cd = PI / 2 * 3;
        break;
      case 2 :
        cx = MAZE_SIZE * mazeWallSize / 2;
        cy = MAZE_SIZE * mazeWallSize - mazeWallSize * 1.5f;
        cd = 0;
        break;
      case 3 :
        cx = mazeWallSize * 1.5f;
        cy = MAZE_SIZE * mazeWallSize / 2;
        cd = PI / 2;
        break;
    }
    cmx = cmy = 0;
    csp = 0;
    rcnt = 0;
    ccnt = -8;
    dcnt = 0;
    time = STAGE_TIME;

    // Set a stage pattern.
    stage++;
    int rockNum = 0;
    switch (stage % 6) {
      case 0 :
        mazeGrid = 8;
        mazePtn = 0;
        rockNum = stage * 3;
        flagNum = 4;
        break;
      case 1 :
        mazeGrid = 4;
        mazePtn = 0;
        rockNum = stage - 1;
        flagNum = 0;
        break;
      case 2 :
        mazePtn = 1;
        rockNum = 10 + stage * 4;
        flagNum = 8;
        break;
      case 3 :
        mazeGrid = 6;
        mazePtn = 0;
        rockNum = stage * 3;
        flagNum = 4;
        break;
      case 4 :
        mazeGrid = 3;
        mazePtn = 0;
        rockNum = stage;
        flagNum = 0;
        break;
      case 5 :
        mazeGrid = 6;
        mazePtn = 2;
        rockNum = 12 + stage * 3;
        flagNum = 3;
        break;
    }
    if (rockNum > rock.length)
      rockNum = rock.length;
    if (stage == 0) {
      msg = "Get flags.";
      mcnt = 60;
    } else if (stage == 2) {
      msg = "Avoid rocks.";
      mcnt = 60;
    } else if (flagNum == 0) {
      msg = "Go to the exit.";
      mcnt = 60;
    } else {
      mcnt = 0;
    }

    // Create a maze and set flags and rocks.
    createMaze();
    wx = (int) (cx / mazeWallSize);
    wy = (int) (cy / mazeWallSize);
    maze[wx][wy] = 1;
    for (int i = 0; i < flagNum; i++) {
      int x, y;
      do {
        x = (int) random(MAZE_SIZE - 2) + 1;
        y = (int) random(MAZE_SIZE - 2) + 1;
      } while (maze[x][y] != 0);
      maze[x][y] = 3;
    }
    for (int i = 0; i < rockNum; i++) {
      int x = (int) random(MAZE_SIZE - 2) + 1;
      int y = (int) random(MAZE_SIZE - 2) + 1;
      if (maze[x][y] == 0) {
        maze[x][y] = i + 10;
        rock[i].ox = random(mazeWallSize / 2) + mazeWallSize / 4;
        rock[i].oy = random(mazeWallSize / 2) + mazeWallSize / 4;
        rock[i].x = x * mazeWallSize + rock[i].ox;
        rock[i].y = y * mazeWallSize + rock[i].oy;
      }
    }
    maze[wx][wy] = 0;
  }

  /**
   * 迷路の壁と壁の間隔
   */
  int mazeGrid;
  /**
   * 迷路パターン(0-通常、1-なにもなし、2-うずまき)
   */
  int mazePtn;
  /**
   * 壁を延ばした地点の数(迷路作成終了判定用)
   */
  int pointNum;
  /**
   * 壁を延ばしている座標
   */
  int stx, sty, std;
  /**
   * 壁を延ばす方向の移動量定数
   */
  int[] DMX = { 0, 1, 0, -1 }, DMY = { -1, 0, 1, 0 };
  /**
   * うずまき型迷路作成用カウンタ
   */
  int turnCnt;

  /**
   * 壁を延ばして迷路作成
   */
  void createMaze() {
    for (int x = 0; x < MAZE_SIZE; x++)
      maze[x][0] = maze[x][MAZE_SIZE - 1] = 1;
    for (int y = 1; y < MAZE_SIZE - 1; y++) {
      maze[0][y] = maze[MAZE_SIZE - 1][y] = 1;
      for (int x = 1; x < MAZE_SIZE - 1; x++)
        maze[x][y] = 0;
    }
    int ep = (MAZE_SIZE - EXIT_SIZE) / 2;

    int bn = 0;
    switch (mazePtn) {
      case 0 :
        bn = 100;
        pointNum = MAZE_SIZE / mazeGrid;
        pointNum -= 1;
        pointNum *= pointNum;
        pointNum -= (int) ((EXIT_SIZE + 1) / mazeGrid)
          * (int) ((EXIT_SIZE + 1) / mazeGrid);
        break;
      case 1 :
        bn = 0;
        pointNum = 0;
        break;
      case 2 :
        bn = 1;
        pointNum = 999;
        turnCnt = 0;
        break;
    }
    for (int i = 0; i < bn; i++) {
      if (pointNum <= 0)
        break;
      if (mazePtn == 0) {
        stx =
          (int) random((MAZE_SIZE - 1) / mazeGrid - 1) * mazeGrid
            + mazeGrid;
        sty =
          (int) random((MAZE_SIZE - 1) / mazeGrid - 1) * mazeGrid
            + mazeGrid;
        std = (int) random(4);
        if (maze[stx][sty] == 1) {
          if (!moveTillNoWall())
            continue;
        } else {
          if (!moveTillWall())
            continue;
        }
      } else {
        switch (exitD) {
          case 0 :
            stx = ((int) (MAZE_SIZE / mazeGrid) / 2 + 1) * mazeGrid;
            sty = 0;
            std = 2;
            break;
          case 1 :
            stx = MAZE_SIZE - 1;
            sty = ((int) (MAZE_SIZE / mazeGrid) / 2 + 1) * mazeGrid;
            std = 3;
            break;
          case 2 :
            stx = ((int) (MAZE_SIZE / mazeGrid) / 2 - 1) * mazeGrid;
            sty = MAZE_SIZE - 1;
            std = 0;
            break;
          case 3 :
            stx = 0;
            sty = ((int) (MAZE_SIZE / mazeGrid) / 2 - 1) * mazeGrid;
            std = 1;
            break;
        }
      }
      createMazeBranch();
    }

    // Create the exit hole.
    fillMaze(ep - mazeGrid + 1, EXIT_SIZE + (mazeGrid - 1) * 2, 0);
    fillMaze(ep, EXIT_SIZE, 1);
    fillMaze(ep + 1, EXIT_SIZE - 2, 2);
    exitD = (int) random(4);
    int ex, ey;
    switch (exitD) {
      case 0 :
      case 2 :
        ex = ep + 2;
        if (exitD == 0)
          ey = ep;
        else
          ey = ep + EXIT_SIZE - 1;
        for (int x = ex; x < ex + EXIT_SIZE - 4; x++)
          maze[x][ey] = 0;
        break;
      default :
        ey = ep + 2;
        if (exitD == 3)
          ex = ep;
        else
          ex = ep + EXIT_SIZE - 1;
        for (int y = ey; y < ey + EXIT_SIZE - 4; y++)
          maze[ex][y] = 0;
        break;
    }
  }

  /**
   * 壁がない地点まで移動(壁延ばし開始地点探索用)
   * @return 地点が見つかった
   */
  boolean moveTillNoWall() {
    int tx = stx, ty = sty;
    for (;;) {
      tx += DMX[std] * mazeGrid;
      ty += DMY[std] * mazeGrid;
      if (tx < 0 || tx >= MAZE_SIZE || ty < 0 || ty >= MAZE_SIZE)
        return false;
      if (maze[tx][ty] == 0)
        return true;
      stx = tx;
      sty = ty;
    }
  }
  /**
   * 壁がある地点まで移動(壁延ばし開始地点探索用)
   * @return 地点が見つかった
   */  
  boolean moveTillWall() {
    for (;;) {
      stx -= DMX[std] * mazeGrid;
      sty -= DMY[std] * mazeGrid;
      if (stx < 0 || stx >= MAZE_SIZE || sty < 0 || sty >= MAZE_SIZE)
        return false;
      if (maze[stx][sty] == 1)
        return true;
    }
  }

  /**
   * 壁を延ばす(一定の確率で曲がり、四方がふさがれたら終了)
   */
  void createMazeBranch() {
    for (;;) {
      if (maze[stx + DMX[std] * mazeGrid][sty + DMY[std] * mazeGrid]
        == 0) {
        for (int i = 0; i < mazeGrid; i++) {
          stx += DMX[std];
          sty += DMY[std];
          maze[stx][sty] = 1;
        }
        if (mazePtn == 2) {
          if ((turnCnt % 6) == 0) {
            std++;
            std &= 3;
            turnCnt++;
          }
        } else {
          if (random(1) < 0.4f) {
            std += (int) random(2) * 2 - 1;
            std &= 3;
          }
        }
        pointNum--;
        if (pointNum <= 0)
          return;
        continue;
      } else {
        if (mazePtn == 2) {
          std++;
          std &= 3;
          turnCnt++;
        } else {
          std += (int) random(2) * 2 - 1;
          std &= 3;
        }
        if (maze[stx + DMX[std] * mazeGrid][sty + DMY[std] * mazeGrid]
          == 0)
          continue;
        std += 2;
        std &= 3;
        if (maze[stx + DMX[std] * mazeGrid][sty + DMY[std] * mazeGrid]
          == 1)
          return;
      }
    }
  }

  /**
   * 出口作成用に迷路を正方形に埋める
   * @param p 左上の座標
   * @param w 大きさ
   * @param v 埋める値
   */
  void fillMaze(int p, int w, int v) {
    for (int y = p; y < p + w; y++)
      for (int x = p; x < p + w; x++)
        maze[x][y] = v;
  }

  /**
   * 1フレームごとの処理
   */
  void loop() {
    if (state == IN_GAME)
      loopGame();
    else
      loopTitle();
  }

  /**
   * マウスリリース時にゲームスタートするためのフラグ
   */
  boolean startPressed = false;

  /**
   * タイトル時の1フレーム処理
   */
  void loopTitle() {
    background(255);
    cx += cmx;
    cy += cmy;
    wx = (int) (cx / mazeWallSize);
    wy = (int) (cy / mazeWallSize);
    if (cx < 0 || cx > MAZE_SIZE * mazeWallSize)
      cmx *= -1;
    if (cy < 0 || cy > MAZE_SIZE * mazeWallSize)
      cmy *= -1;
    drawMaze();
    boolean onStart;
    if (mouseX > width / 2 - 50
      && mouseX < width / 2 + 50
      && mouseY > height - 100
      && mouseY < height - 75)
      onStart = true;
    else
      onStart = false;
    push();
    translate(0, 0, 30);
    textSize(42);
    textMode(ALIGN_CENTER);
    fill(0, 0, 0);
    text("RallX", width / 2, height / 3);
    stroke(250, 100, 50);
    if (onStart)
      fill(250, 180, 100);
    else
      fill(200, 150, 50);
    strokeWeight(2);
    rect(width / 2 - 50, height - 100, 100, 25);
    strokeWeight(1);
    fill(0, 0, 0);
    textSize(25);
    text("START", width / 2, height - 80);
    pop();
    drawStatus();
    if (onStart && mousePressed)
      startPressed = true;
    if (!mousePressed && startPressed) {
      startPressed = false;
      if (onStart)
        startGame();
    }
  }

  /**
   * ゲーム中の1フレーム処理
   */
  void loopGame() {
    background(255);
    float pcx = cx, pcy = cy;
    cx += cmx;
    cy += cmy;
    cmx += (sin(cd) * csp - cmx) * 0.04f;
    cmy += (cos(cd) * csp - cmy) * 0.04f;
    int mox = mouseX - width / 2, moy = mouseY - height / 2;
    float mouseDst = sqrt(mox * mox + moy * moy);
    float brk = 0.85f + (mouseDst / (width / 2)) * 0.15f;
    if (brk > 1)
      brk = 1;
    cmx *= brk;
    cmy *= brk;
    cmd = atan2(cmx, cmy);
    cmsp = sqrt(cmx * cmx + cmy * cmy);
    score += cmsp * 0.05f;
    float md = adjustDeg(atan2(mox, moy) - cd);
    cd += md * 0.2f;
    cd = adjustDeg(cd);
    wx = (int) (cx / mazeWallSize);
    wy = (int) (cy / mazeWallSize);
    int pwx = (int) (pcx / mazeWallSize);
    int pwy = (int) (pcy / mazeWallSize);
    if (maze[wx][wy] == 1) {
      if (maze[pwx][wy] == 0) {
        cx = pcx;
        cmx *= -0.8f;
      } else if (maze[wx][pwy] == 0) {
        cy = pcy;
        cmy *= -0.8f;
      } else {
        cx = pcx;
        cmx *= -0.8f;
        cy = pcy;
        cmy *= -0.8f;
      }
    } else if (maze[wx][wy] == 2 && ccnt == 0 && flagNum <= 0) {
      ccnt = 1;
      stop(clearSe);
      play(clearSe);
    } else if (maze[wx][wy] == 3) {
      maze[wx][wy] = 0;
      score += 500;
      flagNum--;
      stop(flagSe);
      play(flagSe);
      if (flagNum <= 0) {
        msg = "Go to the exit.";
        mcnt = 60;
      }
    } else if (maze[wx][wy] >= 10) {
      int ri = maze[wx][wy] - 10;
      if (abs(rock[ri].x - cx) + abs(rock[ri].y - cy) < 20)
        miss();
    }
    float od = abs(adjustDeg(cd - cmd));
    if (od > 0.5f && cmsp > 6)
      addParticle(
        width / 2,
        height / 2,
        cmd + PI + random(-0.3f, 0.3f),
        cmsp);
    if (mousePressed) {
      csp += (5 - csp) * 0.3f;
      if (rcnt < 8)
        rcnt++;
    } else {
      csp += (20 - csp) * 0.3f;
      if (rcnt > 0)
        rcnt--;
    }
    if (ccnt > 0) {
      ccnt++;
      rcnt = 0;
      cmx *= 0.8;
      cmy *= 0.8;
      csp *= 0.6;
      if (time > 0) {
        time -= 30;
        score += 100;
        if (time > 30) {
          time -= 30;
          score += 100;
        }
      }
      if (ccnt > 30) {
        initStage();
        return;
      }
    } else if (ccnt < 0) {
      ccnt++;
    }
    if (dcnt > 0) {
      dcnt--;
      rcnt = 0;
      cmx *= 0.8;
      cmy *= 0.8;
      csp *= 0.6;
      if (dcnt > 30) {
        float v = 1 - (float) (250 - dcnt) / 60;
        if (v < 0)
          v = 0;
        volume(bgm, v);
        csp = 0;
        push();
        translate(0, 0, 30);
        textSize(32);
        textMode(ALIGN_CENTER);
        fill(0, 0, 0);
        text("Game Over", width / 2, height / 3);
        pop();
        if (dcnt < 100 || (dcnt < 230 && mousePressed)) {
          startTitle();
          return;
        }
      }
      if (dcnt == 1)
        time = STAGE_TIME;
    }
    if (dcnt == 0 && ccnt == 0) {
      time--;
      if (time <= 0)
        miss();
    }
    if (rcnt < 8) {
      push();
      if (ccnt > 0) {
        translate(0, 0, ccnt * 5);
        ca = 255 - ccnt * 12;
        if (ca < 0)
          ca = 0;
      } else {
        translate(0, 0, -rcnt * 30);
        ca = 255 - rcnt * 31;
        if (ccnt < 0)
          ca = 255 + ccnt * 31;
      }
      if (dcnt <= 0)
        drawCar(width / 2, height / 2, cd, cmd);
      drawMaze();
      noStroke();
      for (int i = 0; i < particle.length; i++)
        if (particle[i].cnt >= 0)
          particle[i].loop();
      pop();
    }
    if (rcnt > 0)
      drawMazeOnRadar();
    drawStatus();
  }

  /**
   * 自車が岩に突っ込んだ/時間切れ
   */
  void miss() {
    stop(crashSe);
    play(crashSe);
    dcnt = 30;
    for (int i = 0; i < 24; i++)
      addParticle(width / 2, height / 2, random(PI * 2), random(20));
    maze[wx][wy] = 0;
    left--;
    if (left < 0)
      dcnt = 250;
  }

  /**
   * 方向を -PI〜PI に収めることで曲がる方向の計算を楽に
   * @param d 補正前の角度
   * @return 補正後の角度
   */
  float adjustDeg(float d) {
    if (d > PI)
      return d - PI * 2;
    else if (d < -PI)
      return d + PI * 2;
    else
      return d;
  }

  /**
   * 車を書く
   * @param x x座標
   * @param y y座標(残機表示兼用のため座標が指定できる)
   * @param d1 前輪の方向(マウスの方向を向く)
   * @param d2 後輪の方向(移動方向を向く後輪)
   */
  void drawCar(int x, int y, float d1, float d2) {
    fill(100, 150, 250, ca);
    stroke(150, 200, 255, ca);
    push();
    translate(x, y);
    rotateZ(-d1);
    translate(0, 6);
    rect(-3, -6, 6, 12);
    pop();
    push();
    translate(x, y);
    rotateZ(-d2);
    translate(-6, -6);
    rect(-3, -6, 6, 12);
    translate(12, 0);
    rect(-3, -6, 6, 12);
    pop();
  }

  /**
   * スクロール画面上の迷路表示
   */
  void drawMaze() {
    float wsx, wsy;
    float swsx = -cx + (wx - wallSightRange) * mazeWallSize + width / 2;
    float swsy = -cy + (wy - wallSightRange) * mazeWallSize + height / 2;
    wsy = swsy;
    beginShape(QUADS);
    for (int y = wy - wallSightRange;
      y <= wy + wallSightRange;
      y++, wsy += mazeWallSize) {
      wsx = swsx;
      for (int x = wx - wallSightRange;
        x <= wx + wallSightRange;
        x++, wsx += mazeWallSize) {
        if (x >= 0 && x < MAZE_SIZE && y >= 0 && y < MAZE_SIZE) {
          if (maze[x][y] == 1)
            drawWall(wsx, wsy);
          else if (maze[x][y] == 2)
            drawGoal(wsx, wsy);
          else if (maze[x][y] == 3)
            drawFlag(wsx, wsy);
          else if (maze[x][y] >= 10) {
            int ri = maze[x][y] - 10;
            drawRock(wsx, wsy, rock[ri].ox, rock[ri].oy);
          }
        }
      }
    }
    endShape();
  }

  /**
   * 壁のz軸方向高さ
   */
  float WALL_height = 20;

  /**
   * 壁を書く
   * @param sx スクロール画面上のx座標
   * @param sy スクロール画面上のy座標
   */
  void drawWall(float sx, float sy) {
    fill(200, 250, 50, ca);
    stroke(100, 150, 100, ca);
    vertex(sx, sy, WALL_height);
    vertex(sx + mazeWallSize, sy, WALL_height);
    vertex(sx + mazeWallSize, sy + mazeWallSize, WALL_height);
    vertex(sx, sy + mazeWallSize, WALL_height);
    fill(220, 250, 150, ca);
    stroke(150, 200, 100, ca);
    vertex(sx, sy, WALL_height);
    vertex(sx + mazeWallSize, sy, WALL_height);
    vertex(sx + mazeWallSize, sy, 0);
    vertex(sx, sy, 0);
    vertex(sx + mazeWallSize, sy, WALL_height);
    vertex(sx + mazeWallSize, sy + mazeWallSize, WALL_height);
    vertex(sx + mazeWallSize, sy + mazeWallSize, 0);
    vertex(sx + mazeWallSize, sy, 0);
    vertex(sx + mazeWallSize, sy + mazeWallSize, WALL_height);
    vertex(sx, sy + mazeWallSize, WALL_height);
    vertex(sx, sy + mazeWallSize, 0);
    vertex(sx + mazeWallSize, sy + mazeWallSize, 0);
    vertex(sx, sy + mazeWallSize, WALL_height);
    vertex(sx, sy, WALL_height);
    vertex(sx, sy, 0);
    vertex(sx, sy + mazeWallSize, 0);
  }

  /**
   * 旗を回収し終わっていたらゴールを書く
   * @param sx スクロール画面上のx座標
   * @param sy スクロール画面上のy座標
   */
  void drawGoal(float sx, float sy) {
    if (flagNum > 0)
      return;
    fill(250, 200, 150, ca);
    stroke(250, 150, 200, ca);
    vertex(sx, sy, 0);
    vertex(sx + mazeWallSize, sy, 0);
    vertex(sx + mazeWallSize, sy + mazeWallSize, 0);
    vertex(sx, sy + mazeWallSize, 0);
  }

  /**
   * 岩を書く
   * @param x スクロール画面上のブロックのx座標
   * @param y スクロール画面上のブロックのx座標
   * @param ox ブロック座標からの補正xオフセット
   * @param oy ブロック座標からの補正yオフセット
   */
  void drawRock(float x, float y, float ox, float oy) {
    float sx = x + ox, sy = y + oy;
    sy -= 3;
    stroke(250, 50, 100, ca);
    fill(200, 100, 50, ca);
    vertex(sx - 8, sy - 8, 0.2f);
    vertex(sx + 8, sy - 8, 0.2f);
    vertex(sx + 8, sy + 8, 0.2f);
    vertex(sx - 8, sy + 8, 0.2f);
    fill(240, 200, 100, ca);
    sx += 5;
    sy += 8;
    vertex(sx - 8, sy - 8, 0.1f);
    vertex(sx + 8, sy - 8, 0.1f);
    vertex(sx + 8, sy + 8, 0.1f);
    vertex(sx - 8, sy + 8, 0.1f);
    fill(200, 220, 120, ca);
    sx -= 9;
    sy -= 4;
    vertex(sx - 8, sy - 8, 0);
    vertex(sx + 8, sy - 8, 0);
    vertex(sx + 8, sy + 8, 0);
    vertex(sx - 8, sy + 8, 0);
  }

  /**
   * 旗を書く
   * @param x スクロール画面上のx座標
   * @param y スクロール画面上のy座標
   */
  void drawFlag(float x, float y) {
    float sx = x + mazeWallSize / 3, sy = y + mazeWallSize / 3;
    fill(250, 250, 100, ca);
    stroke(250, 100, 50, ca);
    vertex(sx, sy, WALL_height);
    vertex(sx + 3, sy, WALL_height);
    vertex(sx + 3, sy + mazeWallSize / 3 * 2, 0);
    vertex(sx, sy + mazeWallSize / 3 * 2, 0);
    vertex(sx + 3, sy, WALL_height);
    vertex(sx + 13, sy, WALL_height);
    vertex(sx + 13, sy + 10, WALL_height / 3 * 2);
    vertex(sx + 3, sy + 10, WALL_height / 3 * 2);
  }

  /**
   * レーダー画面上の迷路と旗と自車を表示
   */
  void drawMazeOnRadar() {
    int a = rcnt * 32 - 1;
    push();
    translate(0, 0, -WALL_height * 2 + (8 - rcnt) * 20);
    noStroke();
    fill(255, 220, 180, a);
    for (int y = 0; y < MAZE_SIZE; y++)
      for (int x = 0; x < MAZE_SIZE; x++)
        if (maze[x][y] == 1)
          rect(
            x * mazeRectSize,
            y * mazeRectSize,
            mazeRectSize,
            mazeRectSize);
        else if (maze[x][y] == 3) {
          fill(200, 200, 50, a);
          rect(
            x * mazeRectSize,
            y * mazeRectSize,
            mazeRectSize,
            mazeRectSize);
          fill(255, 220, 180, a);
        }
    fill(100, 200, 255, a);
    rect(wx * mazeRectSize, wy * mazeRectSize, mazeRectSize, mazeRectSize);
    pop();
  }

  /**
   * スコアと残機と残りタイムとメッセージを表示
   */
  void drawStatus() {
    push();
    translate(0, 0, 30);
    textSize(26);
    textMode(ALIGN_RIGHT);
    fill(0, 0, 0);
    text((int) score, width - 18, 36);
    if (time > -999) {
      textMode(ALIGN_CENTER);
      textSize(33);
      text((time + 29) / 30, width / 2, height - 20);
    }
    for (int i = 0; i < left; i++)
      drawCar(i * 20 + 30, 30, PI, PI);
    if (mcnt > 0) {
      mcnt--;
      textMode(ALIGN_CENTER);
      textSize(24);
      fill(0, 0, 0);
      text(msg, width / 2, height / 3);
    }
    pop();
  }

  /**
   * スモーク状のパーティクル
   */
  class Particle {
    /**
     * 表示サイズ
     */
    float SIZE = 20;
    /**
     * 位置
     */
    float x, y;
    /**
     * 広がる方向および速度
     */
    float deg, speed;
    /**
     * アルファ値
     */
    int alpha;
    /**
     * 消えるまでのカウンタ
     */
    int cnt;

    /**
     * カウンタを-1に設定して非表示に
     */
    Particle() {
      cnt = -1;
    }

    /**
     * cntを設定して有効にする
     * @param x 初期x座標
     * @param y 初期y座標
     * @param d 方向
     * @param s スピード
     */
    void set(float x, float y, float d, float s) {
      this.x = x;
      this.y = y;
      deg = d;
      speed = s;
      alpha = 200 + (int) random(50);
      cnt = 15 + (int) random(15);
    }

    /**
     * 1フレーム毎の処理
     */
    void loop() {
      x += sin(deg) * speed;
      y += cos(deg) * speed;
      speed *= 0.95f;
      alpha *= 0.9f;
      cnt--;
      fill(200, 190, 120, alpha);
      ellipse(x, y, SIZE, SIZE);
    }
  }

  /**
   * パーティクルプールから次のインスタンスを指し示すためのインデックス
   */
  int particleIdx = 0;

  /**
   * パーティクル追加
   * @param x 初期x座標
   * @param y 初期y座標
   * @param d 方向
   * @param s スピード
   */
  void addParticle(float x, float y, float d, float s) {
    particleIdx--;
    if (particleIdx < 0)
      particleIdx += particle.length;
    particle[particleIdx].set(x, y, d, s);
  }

  /**
   * 岩(当たると痛い)
   */
  class Rock {
    /**
     * 迷路ブロック内のオフセット
     */
    float ox, oy;
    /**
     * どの迷路ブロックに配置されているか
     */
    float x, y;
  }
}