2010 02 05

テトリスを作るの5

今日もテトリス。 あまり迷う余地のないところから埋めていく。

まずは得点表示。

削除行数に応じた得点を計算し、現在の得点に加算して表示する。 考えるのは、得点の計算について。

同時に消す行が多い方が得点が高いのが基本。 しかし差をつけたところで、1行4回と4行1回の得点が同じでは意味が無いのだな。 欲張りなプレーヤーにブロックを積み重ねさせるために、4行1回の方が圧倒的に高得点になるようにすべきなのだ。 ということで、同時に消した行数の3乗にしてみた。 オリジナルはどうだったかな。

// 得点表示 function Score() { var score = 0; this.init = function () { _show( score = 0 ); }; this.countUp = function ( count ) { _show( score += Math.pow( count, 3 ) ); }; function _show( value ) { document.getElementById( 'score' ).firstChild.nodeValue = value; } }

次にテトリミノ。

昨日は、パターンとオフセットを持たせるつもりだったが、やめた。 と言うか、持ち方を変えた。 単に新しく作るのと、自分を移動や回転した結果のテトリミノを作るのと、両方に対応するためにパターンとオフセットをコンストラクタに渡す。 パターンが渡されなかった場合は、ランダムに自分のパターンを選択する。 そうして決まる自分のパターンのローカル座標とオフセットから、フィールド座標群を予め計算しておく。

// テトリミノ PATTERNS = [ { cells : [ [ 0,-1],[ 0, 0],[ 0, 1],[ 0, 2] ], color : 'cyan' }, { cells : [ [ 0, 0],[ 0, 1], [ 1, 0],[ 1, 1] ], color : 'yellow' }, { cells : [ [ 0, 0],[0, 1], [ 1,-1],[ 1, 0] ], color : 'green' }, { cells : [ [ 0,-1],[ 0, 0], [ 1, 0],[ 1, 1] ], color : 'red' }, { cells : [ [ 0,-1],[ 0, 0],[ 0, 1], [ 1, 1] ], color : 'blue' }, { cells : [ [ 0,-1],[ 0, 0],[ 0, 1], [ 1,-1] ], color : 'orange' }, { cells : [ [ 0,-1],[ 0, 0],[ 0, 1], [ 1, 0] ], color : 'magenta' } ]; function Tetrimino( offset, initialPattern ) { var self = this; var pattern = initialPattern || PATTERNS[ Math.floor( Math.random() * PATTERNS.length ) ]; this.color = pattern.color; this.cells = ( function () { var cells = []; pattern.cells.each( function ( cell ) { cells.push( [ cell[ 0 ] + offset[ 0 ], cell[ 1 ] + offset[ 1 ] ] ); } ); return cells; } )(); this.move = function ( addOffset ) { return new Tetrimino( [ offset[ 0 ] + addOffset[ 0 ], offset[ 1 ] + addOffset[ 1 ] ], pattern ); }; this.turn = function () { var turnedPattern = { cells : [], color : self.color }; pattern.cells.each( function ( cell ) { turnedPattern.cells.push( [ -cell[ 1 ], cell[ 0 ] ] ); } ); return new Tetrimino( offset, turnedPattern ); }; }

テトリミノの初期パターンをテトリミノの外に出したために、何だか中途半端な感じになってしまった。 外に出したのは、中に出さないでって言われたから。 じゃなくて、最初は、新規に作る時も外からパターンを与えるようにするつもりだったから。 テトリミノの初期パターンを知っているのはテトリミノだけの方がいいと思い直したんだけど、初期パターンを中に入れると、コードの見た目が間延びするんだよな。 で、ここでまた見た目を優先。

あと、座標を配列で扱うかどうかで迷って結局そうしたのだが、今尚すっきりしなかったりする。 どう見ても cell.x の方が、 cell[0] よりも自然だから。 でも、初期化とか関数の引数とかたくさんあって、 {x:0,y:0} よりも [0,0] の方が楽なんだよね。 どうするかな。 って、もう書いちゃったんだけど、うーん…。

いっそのこと Array.prototype.x = function () { return this[ 0 ]; }; として、 cell[0] cell.x() と書けるようにするか。 しかし、これも何か違うような…。 しかも文字数増えてるし。

そうそう、いきなり出てきている each() だが、配列の要素に対する繰り返しを楽に書くために ruby からぱくってきたもの。

// 配列の各要素に対して関数fを適用する Array.prototype.each = function ( f ) { for ( var i = 0; i < this.length; i++ ) { f( this[ i ], i ); } return this; };

繰り返す処理である関数fには、引数として配列の要素であるオブジェクトとそのインデックスを渡す。 微妙に無駄な処理が増えるのだが、コードの見た目は、これを使う方が普通にfor文を書くよりすっきりして良いのだ。 ま、何がすっきりなのかは、全く個人的な感想なんだが。

続きはまた明日。