パスワードを忘れた? アカウント作成
185628 journal

j3259の日記: 2.8 の味見: 継続編

日記 by j3259

以下 Tiark Rompf 著A Taste of 2.8: Continuations の和訳。

Scala 2.8 より継続(continuation) という便利な制御フローの抽象観念がサポートされる。継続を用いることで、例えば、コールバック指向でイベント・ドリブンな API を一文ずつ書かれた、より素直なスタイルで使う事ができる。

これは数々のプログラミング作業を簡潔化できる可能性がある:

  • Java NIO を用いた非同期IO
  • Executor やスレッド・プールの使用
  • ウェブ・アプリケーションにおける複数のリクエストをまたいだ制御フロー

Scala 2.8 では以上の例のための新しいAPI は提供されないが、その基となる継続機構が定まった後では色々期待できるだろう。

shift と reset を茶々っと
プログラムのある時点で「捕捉」された継続はそれ以降の「残り」のプログラムを具象化する。興味深いのは、継承は普通の関数値として取り扱うことができ、例えばデータ構造に保存したり、後で呼び出したり、呼び出さなかったり、複数回呼び出したりすることができる。Scala の継続は Scheme や ML などの継承とは違い複合可能なものである(これを「限定」であるという)。どういう事かというと、限定継続は残りの全てのプログラムを具象化するわけではなく、プログラマが定義した一定の境界までの部分的な残りを具象化するということである。

継続は shift と reset という二つの構造によって制御される。shift を呼び出すことで現在の継続を捕捉され、reset が継続がどこまで届くかという境界を定義する。ここに例をあげる:

reset {
  ...
  shift { k: (Int=>Int) =>  // 継続 k は '_ + 1' となる
    k(7)
  } + 1
}
//  結果: 8

もちろん、shift と reset はライブラリ関数であり、新しい予約語ではない。また、文法的には両方を同時に使う必要もない。実際、shift と reset の組み合わせは全く関係ないメソッド間で起こる事もありえる。型システムは万全を保証するため、単独の shift が現れるメソッドの戻り型には @cps という型注釈をつけることを要求します。しかし、多くの場合はこの型注釈はプログラマが気づく事なく自動的に推論されます。

def foo(): Int @cps[Int,Int] = { // 戻り型は省略可
  shift { k: (Int=>Int) =>
    k(7)
  } + 1
}
reset(2 * foo()) // 結果: 16

コールバック指向な API を用いた例を紹介する:

import java.util.{Timer,TimerTask}
 
val timer = new Timer()
 
def sleep(delay: Int) = shift { k: (Unit => Unit) =>
  timer.schedule(new TimerTask {
    def run() = k() // 通常は k をスレッド・プールで実行する
  }, delay)
}
 
println("わーい")
sleep(1000)
println("スレッド無し!")

冒険好きな気分なら、Scala コンパイラのための継承プラグインを試してはいかがだろう(ソース・レポジトリより入手可能)。Scala 2.8 において継承がどのように実装されるのかという込み入った詳細に関してはこの論文 (ICFP'09 にて受理された)を参照のこと。

訳註: 継承に関しては、なんでも継続 とか Scheme/継続が参考になった。

117471 journal

j3259の日記: Eclipse Galileo で Scala & Swing

日記 by j3259

特に書くこともないけど、一応メモ。

なんとなく Eclipse for RCP/Plug-in Developers (182 MB) の Mac OS X (Coacoa) にしてみる。
Scala IDE for Eclipse を入れて、

http://www.scala-lang.org/scala-eclipse-plugin

指示に従ってプロジェクト毎設定を変更する。

Visual Editor の手順は VE/Update に従う。

http://download.eclipse.org/tools/ve/updates/1.4/

余計なものは選択せずに、素直に Visual Editor だけを選ぶ。

Subclipse は 1.6.x を試してみる。

http://subclipse.tigris.org/update_1.6.x

60889 journal

j3259の日記: マルコフ連鎖モンテカルロ法(MCMC)

日記 by j3259

Wikipedia Markov chain Monte Carloマルコフ連鎖モンテカルロ法に和訳した。あんまり理解してないで訳してるので、日本語的にも内容的にも間違ってる所とかあると思うけど、記事として無いよりは下手な訳でもあったほうがいいでしょう。

59683 journal

j3259の日記: 機械学習/パターン識別 3

日記 by j3259
  • 事前確率 P(x) とクラス条件付密度 p(x|ωj)p(ωj) さえあれば,最適な分類器を作れる。実際には問題の確率的構造について完全な知識を持っていることは稀であり,状況に関するあいまいな知識と訓練データが与えられるだけのことが多い。ここで,与えられたサンプルを用いて未知の確率と確率密度を推定して,推定値をもとに分類器を作るというアプローチが取れる。教師ありパターン識別問題では事前確率の推定に特に問題はないが,クラス条件付密度の推定はそう簡単にはいかない。
  • 確率密度 p(x|ωj)p(ωj) が平均μ→i,共分散数列 ∑i の正規分布であると仮定すると,未知の関数の推定から未知のパラメターμ→i,∑iの推定という問題に単純化することができる。このような方法はパラメトリック(parametric)な方法であるといわれる。
  • パラメトリックな方法の代表として最尤法(さいゆうほう)(maximum likelihood estimation; ML)ベイズ推定(Bayesian estimation)がある。
59175 journal

j3259の日記: 機械学習/パターン識別 2

日記 by j3259
  • λ(αij) = { 0 if i = j; 1 if i != j.
    0-1損失関数(zero-one loss function)。決定された状態が真の状態の場合は 0,それ以外の場合は 1 を返す損失関数。この損失関数に関わる統計的リスク(期待損失)は平均誤差率と等価である。定義より条件付リスクは
    R(αi|x→) = ∑(j=1,c){λ(αij)p(ωj|x→)}
      = ∑(i!=j){P(ωj|x→)}
          = 1 - P(ωi|x→)
    リスクを最小化するには事後確率が最大のi を選ぶべきである。つまり,0-1損失の場合の誤差率を最小化するには「i != j である全ての j について P(ωi|x) > P(ωj|x) である状態 ωi を選択する」。
  • gi(x→). 判別関数(discriminant function)。分類器は「i != j である全ての j について gi(x→) > gj(x→) である」場合,特徴ベクトル x→ をにクラスωi に割り当てる。
  • ベイズ分類器も判別関数として表現することができる。リスクありの一般的な場合は gi(x→) = -R(αi|x→)。誤差率最小の場合は,gi(x→) = P(ωi|x→)。
  • 判別関数は大小を比較するためにだけ用いられるため,正の定数を掛けたり,定数を足し引きしてもかまわない。一般には,判別関数は単調増加な関数 f(・)に入れても判別の結果に変わりはない。
    gi(x→) = P(ωi|x→) = p(x→|ωj)P(ωj) / P(x→)
              = p(x→|ωj)P(ωj)
              = ln p(x→|ωj) + ln P(ωj)
    と大幅に式を単純化していける。
  • ベイズ分類器の構造は条件付確率密度(conditional density) p(x→|ωj) と事前確率 P(ωj) に依存するが,さまざまな密度関数の中でも多変量正規分布(multivariate normal),つまりガウス密度(Gaussian density)が注目されてきた。
  • E[f(x)] = ∫f(x)p(x)dx. 密度p(x)におけるスカラー関数f(x)の期待値(expected value)。x が離散の場合は E[f(x)] = ∑(x∈D){f(x)P(x)}。
  • p(x) = 1/sqrt(2πδ)*exp(-1/2 * ((x - μ)/δ))2). 連続単変量正規分布,ガウス密度。
    μ := E[x] = ∫xp(x)dx.
    δ2 := E[(x - μ)2] = ∫(x - μ)2p(x)dx.
    簡易的に p(x) ~ N(μ,δ2) と表記する。
  • p(x→) = 1/{(2π)d/2 * |∑|^1/2} * exp[-1/2(x→ - μ→)T-1(x→ - μ→)]. d次元の多変量正規密度。
    μ→はd成分の平均ベクトル(mean vector),∑は共分散行列(covariance matrix),|∑|は∑の行列式,∑-1は∑の逆行列である。
    簡易的に p(x→) ~ N(μ→,∑) と表記する。
    μ→ := E[x→] = ∫x→p(x→)dx→.
    ∑ := E[(x→ - μ→)(x→ - μ→)T] = ∫(x→ - μ→)(x→ - μ→)Tp(x→)dx→.

参照

57546 journal

j3259の日記: 機械学習/パターン識別 1

日記 by j3259
  • x→ := (x1, x2)T. サンプルから抽出した特徴を並べたもので,特徴ベクトル(feature vector)とよぶ。サンプルが魚の場合は長さ,明度など。
  • ω. 性質,状態(state of nature)。ω = ω1 などとして,サンプルが実際にどのクラスに属するのかを表す。例えば,ω1 はスズキ,ω1 は鮭など。
  • P(ω1). アプリオリ確率,事前確率,プライヤー(prior)。事前の知識による,次に現れるサンプルが ω1 である確率。サンプルに ω1 と ω2 しかない場合は P(ω1) + P(ω2) = 1.
  • 事前確率しか与えられていない場合は P(ω1) > P(ω2) の場合はサンプルは ω1 であり,それ以外の場合は ω2 という意思決定方式(decision rule)を適用できる。
  • p(x|ω). 観測 x を連続確率変数と考えた場合,x の分布はサンプルの状態に依存した p(x|ω) と表すことができる。これをクラス条件付確率密度関数(class-conditional probability density function)とよぶ。
  • p(ωj|x) = p(x|ωj)p(ωj) / p(x). ベイズの公式(Bayes formula)。事後確率 = もっともらしさ * 事前確率 / エビデンス,と書くこともできる。ベイズの公式は x の値を観測することによって,事前確率 p(ωj) をアポステリオリ確率,事後確率,ポステリア(posterior) p(ωj|x),つまり特徴の値が x の場合にサンプルの状態が ωj である確率,に変換できることを示す。
  • p(x|ωj). x に関するωj の「もっともらしさ」,尤度(ゆうど)とよぶ。
  • p(x). 証拠,エビデンス(evidence)。二値の場合は p(x) = ∑(j=1,2){p(x|ωj)p(ωj)}。事後確率の和が 1 になるような係数であるため,決定には影響を及ぼさない。
  • ある決定方式が他の決定方式より優れていることを示すには,その方法の誤差率(probability of error)を計算しなければならない。状態がω1, ω2 の二値の場合,誤差率は ω1と決定した場合は真の状態がω2である確率,ω2と決定した場合は真の状態がω1である確率がそれぞれ誤差率となる。誤差率を最小化したベイズ決定方式は,P(ω1|x) > P(ω2|x) の場合はサンプルはω1 であり,それ以外の場合は ω2 という決定ルールとなる。その誤差率は P(error|x) = min[P(ω1|x), P(ω2|x)]。
  • ここでスカラー値の観測である x を特徴ベクトルの x→ に拡張して考える。特徴ベクトルは d次元の特徴空間(feature space)に存在する。
  • 1,...,ωc} を c個の状態(カテゴリー)からなる有限集合とし,{α1,...,αa} を a個の取りうる行動からなる有限集合とする。損失関数 λ(αij) は状態が ωj のときに行動αiを取った場合の損失を表す。
  • x→ を確率変数ベクトルとすると,事後確率のp(ωj|x→) はベイズ公式により p(ωj|x→) = p(x→|ωj)p(ωj) / p(x→) となる。
  • ある観測が x→ で,行為αiを取ろうと思っているとする。
    真の状態が ωjの場合,定義より λ(αij)の損失をこうむることになる。真の状態が ωj である確率は p(ωj|x→) であるため,行動αiを取ることによる期待損失は
    R(αi|x→) = ∑(j=1,c){λ(αij)p(ωj|x→)} と書くことができる。意思決定論の用語では期待損失をリスク(risk)とよび,R(αi|x→) を条件付リスク(conditional risk)とよぶ。
  • 決定方式は全ての観測値 x→ に対する行動を返す行動関数 α(x→) と表すことができる。ある決定方式に対する総合リスク R は決定方式の期待損失である。
    R = ∫ R(α(x→)|x→)p(x→)dx→。リスクを最小化するためには,条件付リスクを全ての行動αiについて計算しR(αi|x→)が最小の行動αiを選ぶ。その結果の最小化された総合リスクはベイズリスク(Bayes risk)とよばれ,R*で表される。
  • ベイズ決定方式の特殊形である二値分類問題について考える。α1 を状態がω1であると意思決定することと定義し,α2も同様に定義する。便宜的に,λij := λ(αij),つまり真の状態が ωj であるときに状態が ωi であると意思決定したときのリスクと定義する。
  • リスク最小化方式を事後確率で表すと,
    21 - λ11)P(ω1|x→) > (λ12 - λ22)P(ω2|x→) であるときには状態がω1であると決定すると表すことができる。
  • ベイズ公式を適用し,さらに(λ21 > λ11)と仮定すると,
    p(x→|ω1)/p(x→|ω2) > (λ12 - λ22)/(λ21 - λ11) * p(ω2)/p(ω1) であるときには状態がω1であると決定すると表すことができる。

参照

39391 journal

j3259の日記: Writing Tetris using Google Android (アンドロイドでテトリスを書く)

日記 by j3259

T-Mobile G1を入手したのを機に,Android を勉強するためにテトリスを書く。Ruby でテトリスを書くを参考にしつつ,多少改良を加えた。

main.xml, Tetrix.java (Activity), TetrixThread.java は Android サンプルの Lunar Lander を参考にして View を作ってるだけ。ジェスチャーの自動検知をセットアップして,TetrixThread にて fling(指をスライドさせる動作)のx,y秒速から角度を計算してそれぞれにアクションに割り当ててるのが Androidっぽいかも。

以下は特に Android と関係なくて Java のクラスをどうするかって話だけど,何でもかんでもボードに詰め込むんじゃなくて,Game というクラスを作って,Board はゲームのロジックに関知しないようにしたところすっきりした。Ruby のブロックを真似た Game.rotate にしようとしたけど,匿名クラスを毎回生成するっていう富豪なコードになってしまった。毎回 new することもないんだろうけど,いいでしょう。

    public boolean rotate() {
        return transform(new Transformable() {
            @Override
            public void transform() {
                m_currentBlock.rotate(Math.PI / 2.0);
            }
        });
    }

ブロックに関しても,char じゃなくて列挙型でちゃんと定義して,ブロックの初期形も列挙内で定義した。

    T(0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f),

----------->8---------------
main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
        <org.doubletype.gtetrix.TetrixView android:id="@+id/tetrix_view"
            android:layout_height="fill_parent"
            android:layout_width="fill_parent"
            />
</LinearLayout>

----------->8---------------

Tetrix.java

package org.doubletype.gtetrix;
 
import android.app.Activity;
import android.os.Bundle;
 
public class Tetrix extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

----------->8---------------

TetrixView.java

package org.doubletype.gtetrix;
 
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
 
public class TetrixView extends SurfaceView implements SurfaceHolder.Callback {
    private TetrixThread m_thread;
 
    public TetrixView(Context a_context, AttributeSet a_attrs) {
        super(a_context, a_attrs);
 
        // register our interest in hearing about changes to our surface
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
 
        // create thread only; it's started in surfaceCreated()
        m_thread = new TetrixThread(holder, a_context, new Handler() {
            @Override
            public void handleMessage(Message m) {
                // mStatusText.setVisibility(m.getData().getInt("viz"));
                // mStatusText.setText(m.getData().getString("text"));
            }
        });
 
        setFocusable(true); // make sure we get key events
        setLongClickable(true);
        setGesture();
    }
 
    private void setGesture() {
        final GestureDetector gestureDetector = new GestureDetector(
                new GestureDetector.SimpleOnGestureListener() {
                    @Override
                    public boolean onFling(MotionEvent e1, MotionEvent e2,
                            float velocityX, float velocityY) {
                        m_thread.addFling(velocityX, velocityY);
                        return true;
                    }
                });
 
        this.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return gestureDetector.onTouchEvent(event);
            }
        });
    }
 
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        m_thread.setSurfaceSize(width, height);
    }
 
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // start the thread here so that we don't busy-wait in run()
        // waiting for the surface to be created
        m_thread.setRunning(true);
        m_thread.start();
    }
 
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // we have to tell thread to shut down & wait for it to finish, or else
        // it might touch the Surface after we return and explode
        boolean retry = true;
        m_thread.setRunning(false);
        while (retry) {
            try {
                m_thread.join();
                retry = false;
            } catch (InterruptedException e) {
            }
        }
    }
}

----------->8---------------
TetrixThread.java

package org.doubletype.gtetrix;
 
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Handler;
import android.util.Log;
import android.view.SurfaceHolder;
 
public class TetrixThread extends Thread {
    private final String TAG = "TetrixThread";
 
    private SurfaceHolder m_holder;
    private Handler m_handler;
    private Context m_context;
    private boolean m_isRunning;
    private Paint m_black;
    private long m_fps;
    private int m_targetFps;
    private int m_frameQuantum;
    private int m_canvasWidth;
    private int m_canvasHeight;
    private Game m_game;
 
    public TetrixThread(SurfaceHolder a_holder, Context a_context,
            Handler a_handler) {
        // get handles to some important objects
        m_holder = a_holder;
        m_handler = a_handler;
        m_context = a_context;
 
        // Resources res = a_context.getResources();
        m_targetFps = 30;
        m_frameQuantum = 1000 / m_targetFps;
 
        m_black = new Paint();
        m_black.setAntiAlias(false);
        m_black.setARGB(255, 0, 0, 0);
 
        m_game = new Game();
    }
 
    @Override
    /**
     */
    public void run() {
        Log.i(TAG, "run");
 
        long diffs[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        long diff;
        int diffPos = 0;
        int frame = 0;
 
        while (m_isRunning) {
            long start = System.currentTimeMillis();
 
            Canvas canvas = null;
            try {
                canvas = m_holder.lockCanvas(null);
                synchronized (m_holder) {
                    if (frame++ == 0) {
                        m_game.tick();
                    } else {
                        if (frame == m_targetFps) {
                            frame = 0;
                        } // if
                    } // else
 
                    doDraw(canvas);
                }
            } finally {
                // do this in a finally so that if an exception is thrown
                // during the above, we don't leave the Surface in an
                // inconsistent state
                if (canvas != null) {
                    m_holder.unlockCanvasAndPost(canvas);
                } // if
            } // try-finally
 
            diff = System.currentTimeMillis() - start;
            if (diff < m_frameQuantum && m_isRunning) {
                try {
                    Thread.sleep(m_frameQuantum - diff);
                } catch (InterruptedException e) {
                    // e.printStackTrace();
                }
            } // if
 
            diff = System.currentTimeMillis() - start;
            if (++diffPos > 9) {
                diffPos = 0;
            } // if
            diffs[diffPos] = diff;
            long total = 0;
            for (long value : diffs) {
                total += value;
            } // for
            m_fps = 10000 / total;
        }
    }
 
    /**
     */
    private void doDraw(Canvas a_canvas) {
        a_canvas.drawRect(0, 0, m_canvasWidth, m_canvasHeight, m_black);
        m_game.draw(a_canvas);
    }
 
    public void setRunning(boolean b) {
        m_isRunning = b;
    }
 
    public void setSurfaceSize(int a_width, int a_height) {
        // synchronized to make sure these all change atomically
        synchronized (m_holder) {
            m_canvasWidth = a_width;
            m_canvasHeight = a_height;
        } // synchronized
    }
 
    /**
     * @param a_vx
     *            The velocity of this fling measured in pixels per second along
     *            the x axis.
     * @param a_vy
     *            The velocity of this fling measured in pixels per second along
     *            the y axis.
     */
    public void addFling(float a_vx, float a_vy) {
        int theta = (int) Math.toDegrees(Math.atan2(a_vy, a_vx));
        if (theta < 0) {
            theta += 360;
        } // if
        double hypot = Math.hypot(a_vx, a_vy);
        Log.i(TAG, "fling: (" + Integer.toString(theta) + ", "
                + Double.toString(hypot) + ")");
        if (theta < 45 || theta >= 315) {
            synchronized (m_holder) {
                m_game.moveBy(1, 0);
            } // synchronized
 
            return;
        } // if
 
        if (theta >= 45 && theta < 135) {
            synchronized (m_holder) {
                m_game.drop();
            } // synchronized
 
            return;
        } // if
 
        if (theta >= 135 && theta < 225) {
            synchronized (m_holder) {
                m_game.moveBy(-1, 0);
            } // synchronized
 
            return;
        } // if
 
        if (theta >= 225 && theta < 315) {
            synchronized (m_holder) {
                m_game.rotate();
            } // synchronized
 
            return;
        } // if
    }
}

----------->8---------------

Game.java

package org.doubletype.gtetrix;
 
import android.graphics.Canvas;
import android.graphics.Point;
 
public class Game {
    public static final int WIDTH = 9;
    public static final int HEIGHT = 20;
 
    private interface Transformable {
        void transform();
    }
 
    enum GameMode {
        NEW,
        ACTIVE,
        GAME_OVER
    }
 
    private GameMode m_mode;
    private Board m_board;
    private Board m_mini;
    private Block m_currentBlock;
    private Block m_nextBlock;
 
    public Game() {
        m_mode = GameMode.NEW;
        m_board = new Board(new Point(WIDTH, HEIGHT), new Point(10, 10));
        m_mini = new Board(new Point(5, 5), new Point(200, 10));
 
        loadNextBlock();
        loadNewBlock();
    }
 
    public void draw(Canvas a_canvas) {
        m_board.draw(a_canvas);
        m_board.drawBlock(a_canvas, m_currentBlock);
        m_mini.draw(a_canvas);
    }
 
    public boolean loadNextBlock() {
        m_nextBlock = new Block();
        m_nextBlock.moveTo(m_mini.getSize().x / 2,
                m_mini.getSize().y - 3);
 
        m_mini.clear();
        if (!m_mini.isBlockInBoundary(m_nextBlock)
                || !m_mini.isBlockCollide(m_nextBlock)) {
            return false;
        } // if
 
        return m_mini.load(m_nextBlock);
    }
 
    public boolean loadNewBlock() {
        m_currentBlock = m_nextBlock;
        loadNextBlock();
        m_currentBlock.moveTo(m_board.getSize().x / 2,
                m_board.getSize().y - 3);
 
        if (!m_board.isBlockInBoundary(m_currentBlock)
                || !m_board.isBlockCollide(m_currentBlock)) {
            m_mode = GameMode.GAME_OVER;
 
            return false;
        } // if
 
        return m_board.load(m_currentBlock);
    }
 
    public boolean moveBy(final int a_x, final int a_y) {
        if (m_mode == GameMode.NEW) {
            m_mode = GameMode.ACTIVE;
        } // if
 
        return transform(new Transformable() {
            @Override
            public void transform() {
                m_currentBlock.moveBy(a_x, a_y);
            }
        });
    }
 
    public boolean rotate() {
        return transform(new Transformable() {
            @Override
            public void transform() {
                m_currentBlock.rotate(Math.PI / 2.0);
            }
        });
    }
 
    private boolean transform(Transformable a_tansformable) {
        if (m_mode != GameMode.ACTIVE) {
            return false;
        } // if
 
        boolean retval = true;
        Block temp = new Block(m_currentBlock);
        m_board.unload(m_currentBlock);
        a_tansformable.transform();
        if (!m_board.isBlockInBoundary(m_currentBlock)
                || !m_board.isBlockCollide(m_currentBlock)) {
            m_currentBlock = temp;
            retval = false;
        } // if
 
        m_board.load(m_currentBlock);
        return retval;
    }
 
    public boolean tick() {
        if (m_mode != GameMode.ACTIVE) {
            return false;
        } // if
 
        if (moveBy(0, -1)) {
            return true;
        } // if
 
        m_board.checkRows();
        return loadNewBlock();
    }
 
    public boolean drop() {
        while (moveBy(0, -1)) {
        } // while
        return false;
    }
}

----------->8---------------

Board.java

package org.doubletype.gtetrix;
 
import java.util.ArrayList;
 
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Paint.Style;
import android.util.Log;
 
public class Board {
    public static final int CELL_WIDTH = 18;
    public static final int CELL_PADDING = 2;
    private static String TAG = "Board";
    private static Paint s_framePaint = null;
    private static Paint s_blockPaint = null;
    private static Paint s_redPaint = null;
 
    private static Paint getFramePaint() {
        if (s_framePaint == null) {
            s_framePaint = new Paint();
            s_framePaint.setAntiAlias(true);
            s_framePaint.setARGB(255, 100, 100, 100);
            s_framePaint.setStyle(Style.STROKE);
        } // if
 
        return s_framePaint;
    }
 
    private static Paint getBlockPaint() {
        if (s_blockPaint == null) {
            s_blockPaint = new Paint();
            s_blockPaint.setAntiAlias(true);
            s_blockPaint.setARGB(255, 100, 100, 100);
        } // if
 
        return s_blockPaint;
    }
 
    private static Paint getRedPaint() {
        if (s_redPaint == null) {
            s_redPaint = new Paint();
            s_redPaint.setAntiAlias(true);
            s_redPaint.setARGB(255, 200, 100, 100);
        } // if
 
        return s_redPaint;
    }
 
    private Point m_location;
    private Point m_size;
    private int m_cellCount;
    private ArrayList<Rect> m_rectangles;
    private BlockType [] m_cells;
    private Rect m_frame;
 
    public Board(Point a_size, Point a_location) {
        m_size = a_size;
        m_location = a_location;
        m_cellCount = a_size.x * a_size.y;
 
        initRects();
        m_cells = new BlockType[m_cellCount];
        clear();
    }
 
    /**
     * initializes m_rectangles.
     */
    private void initRects() {
        m_rectangles = new ArrayList<Rect>();
        for (int y = 0; y < m_size.y; y++) {
            for (int x = 0; x < m_size.x; x++) {
                Point coord = toCoord(x, y);
                m_rectangles.add(new Rect(coord.x, coord.y,
                        coord.x + CELL_WIDTH, coord.y + CELL_WIDTH));
            } // for x
        } // for y
 
        Point coord = toCoord(m_size.x, -1);
        m_frame = new Rect(m_location.x, m_location.y,
                coord.x, coord.y);
    }
 
    private Point toCoord(int a_x, int a_y) {
        return new Point(a_x * (CELL_WIDTH + CELL_PADDING) + CELL_PADDING + m_location.x,
                (m_size.y - a_y - 1) * (CELL_WIDTH + CELL_PADDING) + CELL_PADDING + m_location.y);
    }
 
    private boolean isCellEmpty(int a_i) {
        return m_cells[a_i] == BlockType.EMPTY;
    }
 
    private boolean isCellEmpty(Point a_pos) {
        return getCell(a_pos) == BlockType.EMPTY;
    }
 
    private boolean isCellEmpty(int a_x, int a_y) {
        return m_cells[a_y * m_size.x + a_x] == BlockType.EMPTY;
    }
 
    private BlockType getCell(Point a_pos) {
        return m_cells[a_pos.y * m_size.x + a_pos.x];
    }
 
    private boolean isCellInBoundary(Point a_pos) {
        return (a_pos.x >= 0
                && a_pos.x < m_size.x
                && a_pos.y >= 0
                && a_pos.y < m_size.y);
    }
 
    private void setCell(Point a_pos, BlockType a_value) {
        m_cells[a_pos.y * m_size.x + a_pos.x] = a_value;
    }
 
    public void clear() {
        for (int i = 0; i < m_cellCount; i++) {
            m_cells[i] = BlockType.EMPTY;
        } // for i
    }
 
    public void draw(Canvas a_canvas) {
        for (int i = 0; i < m_cellCount; i++) {
            if (!isCellEmpty(i)) {
                a_canvas.drawRect(m_rectangles.get(i), getBlockPaint());
            } // if
        } // for i
 
        a_canvas.drawRect(m_frame, getFramePaint());
    }
 
    public void drawBlock(Canvas a_canvas, Block a_block) {
        for (Point cell: a_block.getCells()) {
            a_canvas.drawRect(m_rectangles.get(cell.y * m_size.x + cell.x), getRedPaint());
        }  // for cell
    }
 
    public boolean load(Block a_block) {
        for (Point cell: a_block.getCells()) {
            if (!isCellEmpty(cell)) {
                return false;
            } // if
 
            setCell(cell, a_block.getType());
        } // for cell
 
        return true;
    }
 
    public boolean unload(Block a_block) {
        for (Point cell: a_block.getCells()) {
            setCell(cell, BlockType.EMPTY);
        } // for cell
 
        return true;
    }
 
    public boolean isBlockInBoundary(Block a_block) {
        for (Point cell: a_block.getCells()) {
            if (!isCellInBoundary(cell)) {
                return false;
            } // if
        } // for cell
 
        return true;
    }
 
    public boolean isBlockCollide(Block a_block) {
        for (Point cell: a_block.getCells()) {
            if (!isCellEmpty(cell)) {
                return false;
            } // if
        } // for cell
 
        return true;
    }
 
    public void checkRows() {
        for (int y = m_size.y - 1; y >= 0; y--) {
            if (isRowFilled(y)) {
                removeRow(y);
            } // if
        } // for y
    }
 
    private boolean isRowFilled(int a_y) {
        for (int x = 0; x < m_size.x; x++) {
            if (isCellEmpty(x, a_y)) {
                return false;
            } // if
        } // for x
 
        return true;
    }
 
    private void removeRow(int a_y) {
        Log.i(TAG, "About to remove row " + Integer.toString(a_y));
 
        for (int i = a_y * m_size.x; i < m_cellCount - m_size.x; i++) {
            m_cells[i] = m_cells[i + m_size.x];
        } // for i
 
        for (int i = m_cellCount - m_size.x; i < m_cellCount; i++) {
            m_cells[i] = BlockType.EMPTY;
        } // for i
    }
 
    public Point getSize() {
        return m_size;
    }
}

----------->8---------------

Block.java

package org.doubletype.gtetrix;
 
import android.graphics.Point;
import android.graphics.PointF;
 
public class Block {
    public static int NUM_OF_CELLS = 4;
    private PointF m_pos;
    private BlockType m_type;
    private PointF [] m_locals;
    private Point [] m_cells;
 
    public Block() {
        m_pos = new PointF();
        m_pos.x = 0;
        m_pos.y = 0;
        m_type = BlockType.random();
 
        initPoints();
        update();
    }
 
    public Block(Block a_block) {
        m_pos = new PointF();
        m_pos.set(a_block.m_pos);
        m_type = a_block.m_type;
        m_locals = new PointF[NUM_OF_CELLS];
        m_cells = new Point[NUM_OF_CELLS];
 
        for (int i = 0; i < NUM_OF_CELLS; i++) {
            m_locals[i] = new PointF();
            m_locals[i].set(a_block.m_locals[i]);
            m_cells[i] = new Point(a_block.m_cells[i]);
        } // for
    }
 
    private void initPoints() {
        m_locals = new PointF[NUM_OF_CELLS];
        m_cells = new Point[NUM_OF_CELLS];
 
        for (int i = 0; i < NUM_OF_CELLS; i++) {
            m_locals[i] = new PointF();
            m_locals[i].set(m_type.getPoint(i));
            m_cells[i] = new Point(0, 0);
        } // for i
    }
 
    private void update() {
        for (int i = 0; i < NUM_OF_CELLS; i++) {
            m_cells[i].x = Math.round(m_locals[i].x + m_pos.x);
            m_cells[i].y = Math.round(m_locals[i].y + m_pos.y);
        } // for
    }
 
    public BlockType getType() {
        return m_type;
    }
 
    public Point [] getCells() {
        return m_cells;
    }
 
    public void moveBy(int a_x, int a_y) {
        m_pos.x += a_x;
        m_pos.y += a_y;
        update();
    }
 
    public void moveTo(int a_x, int a_y) {
        m_pos.x = a_x;
        m_pos.y = a_y;
        update();
    }
 
    public void rotate(double a_theta) {
        for (PointF local: m_locals) {
            float x = local.x;
            float y = local.y;
            local.x = (float) (x * Math.cos(a_theta) - y * Math.sin(a_theta));
            local.y = (float) (x * Math.sin(a_theta) + y * Math.cos(a_theta));
        } // local
        update();
    }
}

----------->8---------------

BlockType.java

package org.doubletype.gtetrix;
 
import java.util.Random;
import android.graphics.PointF;
 
public enum BlockType {
    T(0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f),
    X(-0.5f, 0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f),
    L(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, -1.0f),
    J(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, -1.0f, -1.0f),
    B(0.0f, -1.5f, 0.0f, -0.5f, 0.0f, 0.5f, 0.0f, 1.5f),
    S(-0.5f, 0.0f, 0.5f, 0.0f, -0.5f, 1.0f, 0.5f, -1.0f),
    Z(-0.5f, 0.0f, 0.5f, 0.0f, -0.5f, -1.0f, 0.5f, 1.0f),
    EMPTY(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
 
    private static int NUM_OF_BLOCKTYPES = 7;
    private static Random s_rand = new Random();
 
    public static BlockType random() {
        return valueOf(s_rand.nextInt(NUM_OF_BLOCKTYPES));
    }
 
    public static BlockType valueOf(int a_value) {
        switch (a_value) {
        case 0: return T;
        case 1: return X;
        case 2: return L;
        case 3: return J;
        case 4: return B;
        case 5: return S;
        case 6: return Z;
        } // switch
 
        return EMPTY;
    }
 
    private PointF [] m_points;
 
    private BlockType(float a_x0, float a_y0,
            float a_x1, float a_y1,
            float a_x2, float a_y2,
            float a_x3, float a_y3) {
 
        m_points = new PointF[Block.NUM_OF_CELLS];
        m_points[0] = new PointF(a_x0, a_y0);
        m_points[1] = new PointF(a_x1, a_y1);
        m_points[2] = new PointF(a_x2, a_y2);
        m_points[3] = new PointF(a_x3, a_y3);
    }
 
    public PointF getPoint(int a_i) {
        return m_points[a_i];
    }
}

----------->8---------------

38655 journal

j3259の日記: Eclipse 3.4 Ganymede で Swing

日記 by j3259

年を経るにつれて Eclipse に Visual Editor を入れるのが簡単になっていっている。Eclipse で Swingから一年以上経っているので,新しい記事にする。

VE/Installing というページに VE 1.3 と VE 1.4 両方のインストール方法が掲載されているが,せっかくなので新しい方の 1.4 を入れてみる。

  1. Java SE Downloads から Java SE Development Kit (JDK) 6 Update 10 をダウンロードしてインストールする。
  2. Eclipse Download から Eclipse IDE for Java EE Developers (163 MB) をダウンロードしてインストールする。2008年10月現在ではこれは Eclipse 3.4 Ganymede。
  3. VE/Update に書かれた手順に従う。(以下に概要をまとめた)
  1. メニューバー Help > Software Updates... をクリック。
  2. Available Software タブから Add Site... ボタンをクリックして http://update.soyatec.org/Ganymede/ve/1.4 と入力。
  3. ツリーを展開して Visual Editor を選択した後,Install...ボタンをクリック。
  4. Eclipse を再起動する。
  5. メニューバー Help > About Eclipse Platform... をクリック。
  6. Plug-in Detailsボタンをクリックしてプラグインの一覧に Visual Editor があって,バージョンが 1.4以上ならインストール完了。
33924 journal

j3259の日記: Eclipse プロファイラ

日記 by j3259

昔使ったことがある Eclipse のプロファイラがサポートされなくなっていたので,新しいプロファイラを探す。使ってる環境そのものがちょっと古いけど,ちょうどその時期にリリースされた TPTP を見つける。インストールガイドによると Callisto で選ぶだけということなので,Testing and Performance の中から TPTP Reporting with BIRT 4.2.0 と TPTP Profiling for Web applications 以外の4項目をチェックする。Select Required で依存するライブラリも自動チェック。

Java Application Profiling using TPTP によると,Package Navigator のファイル右クリックで Profile As ってメニューが出てくるということなので,Profile As Java Application で実行する。プロファイラを選べと言われたので,Java Profiling を選ぶ。ここで重要なのが,Edit Optionsボタンを押して,どのクラスをプロファイルするかのフィルタを作成すること。デフォルトのフィルタは何でもかんでもフィルタしてしまっているので,int とかしか見ていない。
とりあえず,自分のパッケージはインクルードして他は排除するフィルタを作成。最後に Profileボタンを押すとプロファイリングが始まる。

467504 journal

j3259の日記: Ruby でテトリスを書く

日記 by j3259

既にいくつかのプログラミング言語を使ってプログラミングができる場合,新しい言語を習得するのに手っ取り早い方法はとりあえず使ってみることだと思う。いきなり使えと言われても何を書いたらいいか困るので「お題」があったほうがいい。
テトリスは誰でも基本的な振る舞いを知ってるゲームなので,お題に丁度いい。まず画面に何かを表示しなきゃいけないわけだから GUIライブラリの使うことになるし,ウィジェットがあんまり関係ないグラフィックプラットホーム系のライブラリでも画面を表示するという最低限のことが必要になる。次にボードとかブロックという明らかにオブジェクト指向な「モノ」があるからクラスをどう記述するかが分かる。さらにタイマーイベント,キーボードイベントなどイベントハンドリングも必要。以上はちょっと調べれば分かることだから特にどうってことはない。それでも一応書いてみるっていうのは,読むだけとは違うんだけど。
テトリスは繰り返しっていうか似たような振る舞いをするコードが多いからベタに書こうと思えばベタに書けるけど,それぞれの言語の特性を生かした工夫が色々できる所がある。

例えば,ボードの内容をキャンバスにレンダリングする以下のコード

    @board.each_cell do|x, y, value|
      if value
        @rectangles[y][x].state "normal"
      else
        @rectangles[y][x].state "hidden"
      end # if
    end # do

は Ruby の特徴であるイテレータのブロック呼び出しを使ってみた。さらに value から "normal"/"hidden" に変換するのに関数を書けば一行で書けることになる。

    @board.each_cell {|x, y, value| @rectangles[y][x].state to_state(value) }

require 'tk'
require 'matrix'
 
class RetrixForm
  def initialize(parent)
    @cellSize = 20
    @board = Board.new
 
    @canvas = TkCanvas.new(parent) do
      width 400
      height 500
      background 'white'
      pack
    end # @canvas
 
    Tk.root.bind('Key-Left',  proc {|e| onArrowKeyDown(-1, 0)})
    Tk.root.bind('Key-Right', proc {|e| onArrowKeyDown(1, 0)})
    Tk.root.bind('Key-Up',    proc {|e| onArrowKeyDown(0, 1)})
    Tk.root.bind('Key-Down',  proc {|e| onArrowKeyDown(0, -1)})
    Tk.root.bind('space',     proc {|e| on_spacebar_down()})
    init_rectangles
 
    @menu_spec = [
      [['File', 0],
        {:label=>'Start', :command=>proc{onStart}, :underline=>0},
        '---',
        ['Quit', proc{exit}, 0]
      ]]
    @menubar = parent.add_menubar(@menu_spec, false, nil)
 
    @timer = TkTimer.new(1000) do |timer|
      onTimer
    end.start
    refresh_canvas
  end # initialize
 
  def init_rectangles
    @rectangles = []
    for y in 0...Board::HEIGHT
      row = []
      for x in 0...Board::WIDTH
        ccoord = to_ccoord(x, y)
        rectangle = TkcRectangle.new(@canvas, ccoord[0], ccoord[1],
          ccoord[0] + @cellSize - 2, ccoord[1] + @cellSize - 2,
          'fill' => 'darkgray')
        rectangle.state "hidden"
 
        row << rectangle
      end # for x
      @rectangles << row
    end # for y
 
    @minis = []
    for y in 0...Board::MINI_HEIGHT
      row = []
      for x in 0...Board::MINI_WIDTH
        mcoord = to_mcoord(x, y)
        rectangle = TkcRectangle.new(@canvas, mcoord[0], mcoord[1],
          mcoord[0] + @cellSize - 2, mcoord[1] + @cellSize - 2,
          'fill' => 'darkgray')
        rectangle.state "hidden"
 
        row << rectangle
      end # for x
      @minis << row
    end
  end
 
  def to_ccoord(a_x, a_y)
    [a_x * @cellSize + 50,
      (Board::HEIGHT - a_y - 1) * @cellSize + 2]
  end
 
  def to_mcoord(a_x, a_y)
    [a_x * @cellSize + 100 + Board::WIDTH * @cellSize,
      (Board::MINI_HEIGHT - a_y - 1) * @cellSize + 2]
  end
 
  def onTimer
    return if not @board.active
 
    @board.tick
    refresh_canvas
  end # onTimer
 
  def refresh_canvas
    @board.each_cell do|x, y, value|
      if value
        @rectangles[y][x].state "normal"
      else
        @rectangles[y][x].state "hidden"
      end # if
    end # do
 
    @board.each_mini do|x, y, value|
      if value
        @minis[y][x].state "normal"
      else
        @minis[y][x].state "hidden"
      end # if
    end # do
  end # refresh_canvas
 
  def onStart
    @board.active = true
  end
 
  def onArrowKeyDown(a_deltaX, a_deltaY)
    return if not @board.active
 
    if a_deltaY == 1
      @board.drop
      @board.tick
      refresh_canvas
      return
    end
 
    @board.move_by(a_deltaX, a_deltaY)
    refresh_canvas
  end
 
  def on_spacebar_down
    return if not @board.active
 
    @board.rotate(true)
    refresh_canvas
  end
end
 
class Board
  attr_reader :active
 
  WIDTH = 9
  HEIGHT = 23
  MINI_WIDTH = 5
  MINI_HEIGHT = 5
 
  def Board.in_boundary?(a_x, a_y)
    if (a_x < 0) or (a_x >= WIDTH) or
      (a_y < 0) or (a_y >= HEIGHT)
      return false
    end # if
    true
  end
 
  def initialize(a_block = Block.new)
    @active = false
    @cells = []
    HEIGHT.times { @cells << empty_row }
 
    @mini_cells = []
    MINI_HEIGHT.times { @mini_cells << empty_row(MINI_WIDTH) }
 
    introduce_next_block a_block
    introduce_block
  end # initialize
 
  def empty_row(a_width = WIDTH)
    retval = []
    a_width.times { retval << false }
    return retval
  end
 
  def current_block
    @block
  end
 
  def [](a_y, a_x)
    @cells[a_y][a_x]
  end # []
 
  def []=(a_y, a_x, a_value)
    if not Board.in_boundary?(a_x, a_y)
      raise '(' + a_x.to_s + ', ' + a_y.to_s + ') is out of range'
    end # if
 
    @cells[a_y][a_x] = a_value
  end # []
 
  def set_mini(a_x, a_y, a_value)
    @mini_cells[a_y][a_x] = a_value
  end
 
  def move_by(a_deltaX, a_deltaY)
    transform { @block.move_by(a_deltaX, a_deltaY) }
  end # move_by
 
  def drop
    while move_by(0, -1) do
    end
  end # drop
 
  def rotate(a_clockWise)
    transform { @block.rotate(true) }
  end # rotate
 
  def transform(&a_block)
    retval = true
    map_cells(@block, false)
    temp = @block.dup
    a_block.call
 
    # if moving causes problem, roll it back
    if (not block_in_boundary?(@block)) or (collide? @block)
      # puts current_block.to_s + ' is bad'
      @block = temp
      retval = false
    end # if
 
    map_cells(@block, true)
    # puts current_block
    return retval
  end # transform
 
  def collide? a_block
    a_block.each {|x, y| return true if self[y, x] }
    return false
  end # collide?
 
  def map_cells(a_block, a_value)
    a_block.each {|x, y| self[y, x] = a_value }
  end # map cells
 
  def map_minis(a_block, a_value)
    a_block.each {|x, y| set_mini(x, y, a_value)}
  end
 
  def active=(a_value)
    @active = a_value
  end # active=
 
  def tick
    return if not @active
 
    if move_by(0, -1)
      check_rows
      return
    end # if
 
    introduce_block
  end # tick
 
  def introduce_block
    @block = @next_block
    @block.move_to(WIDTH / 2, HEIGHT - 3)
    introduce_next_block
 
    if (not block_in_boundary?(@block)) or (collide? @block)
      @active = false
      return false
    end # if
 
    map_cells(@block, true)
    return true
  end
 
  def block_in_boundary? (a_block)
    a_block.each {|x, y| return false if not Board.in_boundary?(x, y) }
    return true
  end
 
  def introduce_next_block(a_block = Block.new)
    @next_block = a_block
    clear_minis
    map_minis(@next_block, true)
  end
 
  def clear_minis
    each_mini {|x, y, value| set_mini(x, y, false)}
  end
 
  def check_rows
    (HEIGHT - 1).downto(0) do |i|
      if row_filled? i
        # puts to_s
        remove_row i
      end # if
    end # do
  end
 
  def row_filled? a_row
    retval = true
    @cells[a_row].each {|value| retval = (retval && value) }
    retval
  end
 
  def remove_row(a_row)
    map_cells(@block, false)
    @cells.delete_at(a_row)
    @cells << empty_row
    map_cells(@block, true)
  end
 
  def to_s
    retval = ''
    each_row do |row|
      rowString = ''
      row.each do |cell|
        if cell
          rowString << 'X'
        else
          rowString << '_'
        end # if-else
      end
      retval = rowString + "\n" + retval
    end
    retval
  end
 
  def each_row(&a_block)
    @cells.each {|cell| a_block.call(cell) }
  end
 
  def each_cell(&a_block)
    for y in 0...HEIGHT
      for x in 0...WIDTH
        a_block.call(x, y, self[y, x])
      end # for x
    end # for y
  end # each_cell
 
  def each_mini(&a_block)
    for y in 0...MINI_HEIGHT
      for x in 0...MINI_WIDTH
        a_block.call(x, y, @mini_cells[y][x])
      end # for x
    end # for y
  end
end
 
class Block
  attr_reader :x, :y
 
  def initialize(a_type = rand(6))
    @x = 2.0
    @y = 2.0
    @type = a_type
    @cells = type_to_cells @type
    @icells = Matrix[[0, 0], [0, 0], [0, 0], [0, 0]]
 
    update
  end
 
  def type_to_cells(a_type)
    case a_type
      when 0 # T
        Matrix[[0.0, 0.0], [-1.0, 0.0], [1.0, 0.0], [0.0, 1.0]]
      when 1 # X
        Matrix[[-0.5, 0.5], [0.5, 0.5], [-0.5, -0.5], [0.5, -0.5]]
      when 2 # L
        Matrix[[0.0, 0.0], [0.0, 1.0], [0.0, -1.0], [1.0, -1.0]]
      when 3 # J
        Matrix[[0.0, 0.0], [0.0, 1.0], [0.0, -1.0], [-1.0, -1.0]]
      when 4 # B
        Matrix[[0.0, -1.5], [0.0, -0.5], [0.0, 0.5], [0.0, 1.5]]
      when 5 # S
        Matrix[[-0.5, 0.0], [0.5, 0.0], [-0.5, 1.0], [0.5, -1.0]]
      when 6 # Z
        Matrix[[-0.5, 0.0], [0.5, 0.0], [-0.5, -1.0], [0.5, 1.0]]
    end # case
  end
 
  def [](a_y, a_x)
    @icells[a_y, a_x]
  end # []
 
  def each(&a_block)
    for i in 0..3 do
      a_block.call(self[i, 0], self[i, 1])
    end # for i
  end
 
  def to_s
    @icells.to_s + ' ' + x.to_s + ', ' + y.to_s
  end
 
  def move_by(a_deltaX, a_deltaY)
    move_to(@x + a_deltaX, @y + a_deltaY)
  end # move_by
 
  def move_to(a_x, a_y)
    @x = a_x.to_f
    @y = a_y.to_f
    update
  end
 
  def rotate(a_clockWise)
    rows = []
 
    for i in 0..3 do
      row = (rotation_matrix22(-Math::PI / 2.0) * @cells.row(i)).map do |value|
        (value * 10.0).round / 10.0
      end # map
      rows << row
    end # for
    @cells = Matrix.rows(rows)
 
    update
  end # rotate
 
  def update
    @icells = (@cells + position_matrix).round
  end # update
 
  def position_matrix
    Matrix[[@x, @y], [@x, @y], [@x, @y], [@x, @y]]
  end # position_matrix
 
  def rotation_matrix22(a_r)
    Matrix[ [Math.cos(a_r), -Math.sin(a_r)],
            [Math.sin(a_r), Math.cos(a_r)]]
  end # rotation_matrix22
end
 
class Matrix
  def round
    self.map do |x|
      if x >= 0
        x.round
      else
        -((x + 0.5).abs.round)
      end # if
    end
  end # to_i
end
 
root = TkRoot.new { title "Retrix" }
RetrixForm.new(root)
Tk.mainloop

typodupeerror

クラックを法規制強化で止められると思ってる奴は頭がおかしい -- あるアレゲ人

読み込み中...