/** * ポントリャーギン、振り子の最短時間停止問題 * 2013/05/21 by rikunora. */ import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Random; public class StopPendulum1 extends AppBase implements MouseListener { private static final long serialVersionUID = 6748214024757179955L; double x, y; // 位置 double vx, vy; // 速度 double t = 0.0; // 時刻 // final double dt=0.1; // 計差精度粗くしてみる final double dt=0.05; // 計算精度標準 // final double dt=0.008; // 高精度 final double FORCE = 5.0; // ブレーキ力 double fuel = 0.0; // 消費燃料 final double K = 0.1; // バネ定数 final int RR = 8; // ボールの半径 final int DVY = 2; // 運動量表示(Y軸)の拡大率 // 0:初期状態, 1:ゲーム中, 2:ゲームクリア, 3:ゲームオーバー enum Mode { Init, OnGame, Clear, Over; } Mode game_mode = Mode.Init; Random rnd = new Random(); // 乱数 //// マウス操作 //////////////////////////////////// Point mousePosition = new Point( 0, 0 ); // マウス位置 boolean mousePressed = false; public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } /** ボタン押した */ public void mousePressed(MouseEvent e) { // マウス位置の取得 mousePosition.setLocation(e.getX(), e.getY()); mousePressed = true; if( game_mode == Mode.Init ){ game_mode = Mode.OnGame; // ゲーム中に遷移 } else if( game_mode == Mode.Clear || game_mode == Mode.Over ){ readyModelData(); // 乱数でxを適当な場所に持ってゆく game_mode = Mode.Init;; // 初期状態に遷移 } } /** ボタン離した */ public void mouseReleased(MouseEvent e) { mousePressed = false; } /** X方向にかかる力 = ブレーキ力 */ double forceX() { if( true == mousePressed ){ if( invX(mousePosition.x) >= 0 ){ return -FORCE; // 画面右半分ならマイナス } else { return +FORCE; // 画面左半分ならプラス } } return 0.0; // 何もしない } //// モデル動作 //////////////////////////////////// /** モデルデータの設定 */ protected void initModelData() { // ここでモデル初期値を設定する // super.initModelData(); game_mode = Mode.Init; x = y = 0.0; vx = vy = 0.0; readyModelData(); // 乱数でxを適当な場所に持ってゆく mousePosition = new Point( 0, 0 ); mousePressed = false; this.addMouseListener(this); } /** ゲームの準備 */ protected void readyModelData() { // 乱数でxを適当な場所に持ってゆく int rx = rnd.nextInt(200) + 30; if( rnd.nextInt(1) == 0 ){ rx = -rx; // 符号を反転する } x = (double)rx; // vx は 0 に初期化 vx = 0.0; // パラメータ初期化 t = 0.0; fuel = 0.0; } /** モデルの描画 */ protected void drawModel(Graphics g) { // super.drawModel(g); // 以下で全画面描画しているので親は不要 // 画面の半分を塗る g.setColor( new Color(0xEE, 0xEE, 0xFF) ); g.fillRect( dispX(-WIDTH/2), dispY(-HEIGHT/2), WIDTH/2, HEIGHT ); g.setColor( new Color(0xFF, 0xEE, 0xEE) ); g.fillRect( dispX(0), dispY(-HEIGHT/2), WIDTH/2, HEIGHT ); // 座標軸 g.setColor(Color.BLACK); g.drawLine( dispX(-WIDTH/2), dispY(0), dispX(WIDTH/2), dispY(0) ); g.drawLine( dispX(0), dispY(-HEIGHT/2), dispX(0), dispY(HEIGHT/2) ); // 最適解の円を描く g.setColor(Color.GRAY); int ix; for( ix=0; ix < 3*100; ix += 100 ){ g.drawArc(dispX(ix), dispY(0)-50, 100, 100, 0, 180); } for( ix=-100; ix >= -3*100; ix -= 100 ){ g.drawArc(dispX(ix), dispY(0)-50, 100, 100, 180, 180); } // 位置と運動量 : 位相空間 g.setColor(Color.GREEN); g.fillOval( dispX(x) -RR, dispY(DVY * vx) -RR, 2*RR, 2*RR); // 1次元の位置 g.setColor(Color.BLUE); g.fillOval( dispX(x) -RR, dispY(y) -RR, 2*RR, 2*RR); // パラメータ表示 g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 18)); g.drawString( String.format("t=%.1f, fuel=%.1f", t, fuel ), 10, HEIGHT-10); // メッセージ等の表示 switch( game_mode ){ case Init : drawMessage(g, "READY ... Click to Start!"); break; case OnGame: // ブレーキ力の三角形を描画 double f = forceX(); if( f > 0.0 ){ drawTriangle(g, true); fuel += 0.1; // 燃料消費 } else if( f < 0.0 ){ drawTriangle(g, false); fuel += 0.1; // 燃料消費 } break; case Clear: drawMessage(g, "CLEAR!!"); break; case Over: drawMessage(g, "GAME OVER!"); break; default: break; } } /** 三角形を描画する */ void drawTriangle(Graphics g, boolean sign){ // 三角形の座標 int try_x[] = { -RR, -RR -18, -RR -18 }; int try_y[] = { 0, 12, -12 }; g.setColor(Color.RED); if( sign ){ // プラス方向 for( int i = 0; i < 3; i++ ){ try_x[i] = try_x[i] + dispX(x); try_y[i] += dispY(y); } } else { // マイナス方向 for( int i = 0; i < 3; i++ ){ try_x[i] = - try_x[i] + dispX(x); try_y[i] += dispY(y); } } g.fillPolygon(try_x, try_y, 3); } /** メッセージを描画する */ void drawMessage(Graphics g, String message){ // 影付きで二重描画する g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 24)); g.setColor(Color.yellow); g.drawString(message, 10, 50); g.setColor(Color.BLUE); g.drawString(message, 8, 48); } /** X座標を画面座標に変換 */ int dispX( double x ) { return (int)(x + WIDTH/2); } /** Y座標を画面座標に変換 */ int dispY( double y ) { return (int)(y + HEIGHT/2); } /** 画面からX座標に逆変換 */ double invX(int x) { return (double)(x - WIDTH/2); } /** 画面からY座標に逆変換 */ double invY(int y) { return (double)(y - HEIGHT/2); } /** モデルを動かす */ protected void moveModel() { switch( game_mode ){ case Init: break; case OnGame: moveModel_1(); break; case Clear: break; case Over: break; default: break; } } /** ゲーム中の動き */ void moveModel_1(){ Runge(t, dt); t += dt; // 終了判定 game_mode = checkMove(); } /** ゲームの終了判定 */ Mode checkMove(){ double mov = x * x + vx * vx; if( mov < 6 ){ // 運動が一定以下になったら静止と見なす return Mode.Clear; } else if( mov > 99999 ){ // 一定値を超えたらゲームオーバー return Mode.Over; } return Mode.OnGame; // ゲーム続行 } /** xの導関数 */ double fx(double t, double x, double v) { return v; } /** vxの導関数 */ double fv(double t, double x, double v) { return -K * x + forceX(); } /** * ルンゲ・クッタ法 * 入出力は x, vx を直接書き換える */ void Runge(double t, double dt) { double h1; double kx[] = new double[4], kv[] = new double[4]; double kx1, x1, x2, x3; double kv1, v1, v2, v3; h1 = dt/2.0; kx[0] = fx(t, x, vx) * dt; kv[0] = fv(t, x, vx) * dt; x1 = x + kx[0]/2.0; v1 = vx + kv[0]/2.0; kx[1] = fx(t+h1, x1, v1) * dt; kv[1] = fv(t+h1, x1, v1) * dt; x2 = x + kx[1]/2.0; v2 = vx + kv[1]/2.0; kx[2] = fx(t+h1, x2, v2) * dt; kv[2] = fv(t+h1, x2, v2) * dt; x3 = x + kx[2]; v3 = vx + kv[2]; kx[3] = fx(t+dt, x3, v3) * dt; kv[3] = fv(t+dt, x3, v3) * dt; kx1 = (kx[0] + 2.0*kx[1] + 2.0*kx[2] + kx[3]) / 6.0; kv1 = (kv[0] + 2.0*kv[1] + 2.0*kv[2] + kv[3]) / 6.0; x += kx1; vx += kv1; // xout = x + kx1; // vout = vx + kv1; } }