« 2016年9月 | トップページ | 2016年11月 »

2016年10月

2016年10月30日 (日)

LongDriver のバグ取り

LongDriver のタブレットでの不具合(茶色のバーをタップしても反応しない)は解決しました。茶色のバーにはイベントとして touchstart, touchmove, touchend そして click を割り当てているのですが、その全てで evt.preventDefault() を実行していました。ここに evt は当該イベントです。 touchstarttouchend で、この evt.preventDefault() を外したら茶色のバーがタップを認識してくれました。

最初は evt.preventDefault() が無くて問題が発生し、それではと、全てに evt.preventDefault() を入れたら、それはそれで問題となる。必要な所だけに置けと言うことになるのかも知れませんが、面倒です。

さらに、タブレット、PC両方に共通する問題が1つ有りました。画像の上下に選手のダミーマークが有りますが、それをクリックすると選択モードに入ります。ここで画像下側の茶色バーをクリックするとルート作成モードに入ります。このルート作成モード時にフィールドをスクロールするために画像下側の茶色バー上でドラッグすると、フィールドがスクロールされるだけではなく、茶色バーをクリックした効果も現れ、ルート作成モードを終了します。

この問題を解決するために Chrome のデバロッパーツールでブレイクポイントを設定してステップ実行をしながらバグを見つけようとしたのですが、マウスを茶色バーからデベロッパーツールに移動する必要が有り、それがスクロールと判断されてまともなチェックが出来ません。

さて困ったなと思いながら、この記事を書くために下図を作成していたら気が付きました。 nonDrawisScroll をチェックしていなかったのです。 nonDraw() はルート作成モード時に画像下側の茶色バーがクリックされた時のイベントハンドラーです。状態遷移図から判るように、本当のクリックの時だけでなく hScroll 経由の mouseup の時も nonDraw は実行されるのです。 hScroll 経由のイベントは nonDraw にとって要らないイベントです。そこで nonDrawisScroll のチェックを追加して正常な動作になりました。

\[ \xymatrix{ \ar[r]^<{mousedown} & *[F:<3pt>] \txt{startScroll\\isScroll=false}\ar[r]^{mousemove} \ar[d]_{mouseup=click} & *[F:<3pt>]\txt{hScroll\\isScroll = true} \ar[ld]^{mouseup} \ar[d]^{mouseup} \\ & *[F:<3pt>]\txt{nonDraw\\isScroll?} & **[F:<3pt>]\txt{endScroll} } \]

function nonDraw(evt){
    evt.preventDefault();

    if (ground.isScroll){          // この4行が無かったのがバグ
        ground.isScroll = false;
        return;
    }

    playBook.isDraw = false;
    ground.hbar.onclick = makeRoute;
}

タブレット用のルーチンを実装した、、、けれど

LongDriver にタブレット用のルーチンを実装しました。ただし、バグが有り不完全です。 LongDriver の作成は最初にPCで動くものを作り、その後でタブレットに対応することにしていました。PC限定の時点では、扱うイベントはマウス関連のものでしたが、タブレットに対応するために、タッチイベントを扱うルーチンを追加しました。

機器がPCなのかタブレットなのかを判定して、それに応じてイベントを設定します。 navigator.userAgentAndroidiPad などが有ればタブレットです(下記コード参照)。機器に応じたイベントの文字列を変数に代入しています。後で、その文字列を使ってイベントを処理します。


    var agent = navigator.userAgent;
    if ((0 <= agent.indexOf("Android"))||
        (0 <= agent.indexOf("iPhone"))||
        (0 <= agent.indexOf("iPad"))
    ){
        this.isTouchPnl = true;
        this.mouseDown = "ontouchstart";
        this.mouseMove = "ontouchmove";
        this.mouseUp = "ontouchend";
    } else {
        this.isTouchPnl = false;
        this.mouseDown = "onmousedown";
        this.mouseMove = "onmousemove";
        this.mouseUp = "onmouseup";
    }

タブレットでの最初の動作確認で、フィールドのスクロールの時、画面全体もスクロールするという不具合が有りました。これは、イベント・ハンドラーでイベントのメソッド preventDefault() を実行していなかったのが原因でした。PCでは、マウスでドラッグしても画面がスクロールすることはないので、この不具合は発生していなかったのです。

次の問題(今も解決できていません)は、画像の左右および下側に有る茶色のバーをタップしても、それに反応しないことです。使用しているイベントは click で、これはPCと共通です。画像の上下に有る選手のダミーマークはタップ出来るので、 click には問題は無いと思います。ダミーマークには click しかイベントを割り当てていないのに対して、茶色バーには click 以外にも touchstart, touchmove, touchend を割り当てています。これが不具合に関係しているのかも知れません。

それから、バグではないのですが、選手のマークをドラッグする時、反応が鈍いのも問題です。PCではスムーズに動いているのですが、PCとタブレットの能力は、こんなにも差が有るのでしょうか。

2016年10月26日 (水)

プレイルートの矢印

LongDriver のプレイルートに矢印を表示できるようにしました。下図がサンプルです。

プレイルート(パスルートを含む)上で矢印にしたいマークにフォーカスを当てて注1、画像右上のボタンより [Each][Arrow] をクリックすることで、フォーカスの当たっているマークが矢印になります。

注1: 画像の上下に配置されている選手(およびボール)をクリックするとフィールド上で、それに対応したマークが黄色になります。そのことをフォーカスが当たっていると言います。同じ選手を続けてクリックすると、選手のプレイルート上の節点に順次フォーカスが移動します。

ところで、上図のオフェンス61番はルートの終端がブロックの形になっていますが、これは、そのような形にルートを作成したものです。本来はこれも矢印と同様に専用のマークを用意したいところですが、難しそうなので躊躇しています。矢印の場合はその向きを線と同じ方向に向ければ良いのですが、ブロックの場合は向きをユーザーが任意に設定することになります。そのためのユーザーインターフェースが面倒なのです。そのインターフェースを操作するのと、上図のようにプレイルートをチョイと細工するのは、同じ程度の手間になりそうです。したがって、ブロック専用のマークには対応しないかも知れません。

2016年10月22日 (土)

ボールおよびパスルートの処理

LongDriver ではボールのパスルートを選手のルートで代用していましたが、ボール専用のルートを導入しました。 LongDriver 下段にボールのマークが有りますが、これをクリックするとボールが表示され、後は選手を操作するのと同様にしてルートを作成できます(フィールド内のボールを見えなくするには LongDriver の左右に有る茶色のバーをクリックして下さい)。パスルートの線は破線で表示されます。また、選手のマークは、各選手とも最初のマークが常に表示されていますが、ボールの場合はルート作成の時だけ表示されるようにしました。

ボールのパスルート作成・表示ルーチンは選手のルーチンと基本的に同じですが、線の種類が違ったり、平時は非表示であったりするので、条件分けによってボールの処理をしています。そのせいでコードが汚くなっています。下記コードで /**/ で挟まれたところがボールと選手を区別して処理している箇所です。似たようなものが他にも数か所有ります。


Diagram.prototype.default = function(){
    var id;
    var pos = {};
    var node;
    for (var i = 22; 0 <= i; i--){
        pos.x = this.plyDefPos[i].x - ground.deltX;
        pos.y = this.plyDefPos[i].y - ground.deltY;
        id = this.player[i] + this.index;
        /**/
        if (11 == i){    // ボール
            this.ball = node = this.makeNode(id, pos, 26, this.player[i], true);
            node.setAttribute("opacity", 0.3);
        } else {    // 選手
            node = this.makeNode(id, pos, 26, this.player[i], false);
        }
        /**/
        node.alt = 0;
    }
}

当初は、ボールおよびパスルートは選手および選手のルートを表示するレイヤーとは別のものを用意していたのですが、基本的に、選手と同じルーチンを使用しているため、ボールおよびパスルートも選手と同じレイヤーを使用することにしました。


function Diagram(index, code, prev, next){
    ・・・

    this.routeLayer = document.createElementNS(svgns, "g");
    this.manLayer = document.createElementNS(svgns, "g");
    //this.ballLayer = document.createElementNS(svgns, "g");    不要になった
    ground.field.appendChild(this.routeLayer);
    ground.field.appendChild(this.manLayer);
    //ground.field.appendChild(this.ballLayer);    不要になった

    ・・・
}

2016年10月16日 (日)

エンコード・デコードルーチンの実装(3)

LongDriver のエンコード・デコードルーチンの実装では、不本意な箇所が幾つか有ります。

1つは、プレイブックを新しいものに変更する時、古いプレイブックで使っていた SVG 要素を全部廃棄していることです。 LongDriver 右上の [Total][Edit Full Code] でプレイブックのコードを編集し、それを画像に反映させることが出来ます。その際、仮にプレイブックが複数のプレイ図で構成されており変更されないプレイ図が有ったとしても、全て廃棄してから新たにプレイブック(の SVG 要素)を生成しています。

同じことは1つのプレイ図を変更する時にも当てはまります。右上のボタン [Each][Edit One Code] で1つのプレイ図のコードを編集することが出来ますが、この時も当該プレイ図を廃棄して新しいプレイ図を生成しています。

次に不本意な点は、開始図の処理方法です。ここに開始図とは LongDriver にアクセスしたり、プレイブックを更新した時に最初に表示するプレイ図のことです。プレイブックをデコードして当該プレイ図が開始図となった時、 Diagram.prototype.layout()this.isStart にフラッグをセットして playBook に戻します。ここに、 Diagram はプレイ図のコンストラクターです。 playBook では diagram.isStart をチェックして this.startDgm = diagram を実行します。これにより開始図をプレイブックにセットしています。


function Diagram(index, code, prev, next){
    ・・・
    this.layout(code);
}

Diagram.prototype.layout = function(code){
    ・・・
    case "21":
        pc +=2;
        this.isStart = true;    // (1)
        break;
    ・・・
}

PlayBook.prototype.addDgm = function(dgmCodeAry){
    ・・・
    diagram = new Diagram(this.index++, dgmCodeAry[i], diagram, diagram.next);
    if (diagram.isStart)        // (2)
        this.startDgm = diagram;
    ・・・
}

どれが開始図であるかは、プレイ図そのものに記録するのではなく、プレイブックに記録した方が良いと思います。その方がプログラムにとって都合が良いからです。したがって、当初は、プレイブックへの開始図のセットはプレイ図へのセットを経由することなく直接プレイブックにしようとしました。つまり (1) は次のようにしていました。


    playBook.startDgm = this;    // (1) は当初これだった

しかし、これでは LongDriver 起動時に playBook が未定義というエラーが発生しましたため、 (1), (2) にしたのです。プレイ図へのセットを経由することが必要になったのは設計に問題が有るのだろうと思うのですが、良い解決策が見つからないので、このようなことになりました。

2016年10月15日 (土)

エンコード・デコードルーチンの実装(2)

LongDriver でプレイ図のエンコード・デコードルーチンは1つのプレイ図にしか対応していませんでしたが、これを複数のプレイ図に対応させました(プレイ図が複数集まったものをプレイブックと呼ぶことにします)。併せて、プレイ図の追加・削除のルーチンも実装しました。サンプルを示します。画像右上の三角形をクリックするとプレイ図が切り替わります。

上図は、プレイブックをコード化し、それを LongDriver のアドレスの後ろに添付してアクセスすることで表示しています。そのアドレス&コードは次の通りです。(表示の関係で、適宜改行していますが、本来は1行です)


http://spacelike.in.coocan.jp/football/LongDriver.svg?
PB=1200jL21MbrKlblJobsJwbbJhNbLKlaxKTbCKVarKLOafKlaXKRaSKXacKNP00KlQAfKlAWKRRBLKlSEAKlDtHHT00LHafLPaLLRaeLXacMiUavMNbTLQV00MNaeKxaoJQaqJcaiJbamJSWAvMNAPKdmbDGUnBDGUocnIkpCfIkDSIwDTHeqbpJOrAAIuaZJJsBhJOtbBJzuaQJzvAQJzwBBJz.
McfJxcqHoNbzJxbtKtObTJxbPKqPanJxQaHJxaAKmRAXJxAbKWSFPJyFLGuTanKTamLYaUMhEvGyEeHDEpHMEsGzUbjLZbNLQVaPLaaVKtWBzKYBxHeAXHdmbrFgnAPFgodbHwpETHnEpICErHUqcaIaccHrramIGsBcIjBaIEArIDtbpJLubEJLvaWJLwANJL.
McjHQclCHeuCIeuCINBDHNBDHrOBjHNBiHrPCPHNQCvHNCxHrRDbHNDbHtSFzHOFvCjTCPHjCQJiepCVehCmeWCcepCUUaVHnaDFbBDFbVBcIpDlIKWDLIpByISmBLCwnDTCwoceFJccCzeDCqpFwFQFbDYqaVFqaIFIAoFOrCQFWsDxFqtBNGbuByGbvCgGbwDRGb.

上記コードは LongDriver の右上に有るボタン [Total][Gen. Full Code] をクリックすると表示されます。それをコピーして適宜使用します。この [Gen. Full Code] を実行する時に表示しているプレイ図が、 LongDriver にアクセスした時に最初に表示されます。したがって、プレイブックが複数のプレイ図で構成されている場合、最初に表示させたいプレイ図が表示された状態で [Gen. Full Code] を実行してください。

プレイ図の挿入・削除も [Total] から [New Play], [Delete Play] を選択して実行します。挿入の場合、小さなウィンドウが表示されるので、そこにプレイ図のコードをペーストすると、そのコードに対応したプレイ図が表示されます(プレイ図の位置が、表示されているフィールド位置から外れている場合は、フィールドをスクロールさせないとプレイ図は現れません。左右および下部の茶色のバー上をドラッグするとスクロールします)。コードをペーストせず空のままで [OK] をクリックすると、画面の中央にデフォルトのプレイ図が生成され、それが新しいプレイ図となります。

[Total] の右横に三角形と数字が表示されていますが、これは表示するプレイ図を選択するものです。三角形をクリックするとプレイ図が1つずつ切り替わります。数字が2つ有るうちの左側は現在のプレイ図の番号、右側はプレイ図の総数です。

上記サンプルを見ると分かるように、パスプレイのボールのルートが選手のルートで代用されています。これは改善する予定です。(ところで、ボールのルートは何線で表すのでしょうか?)

2016年10月10日 (月)

もやもやオータムズ(オブジェクトの解放に関して)

JavaScript のオブジェクトをメモリから解放したり HTML 要素を削除することに関してモヤモヤしています。オブジェクトをメモリから解放するのには delete を使用し追記1、 HTML 要素を DOM から除去するのには removeChild を使用します。では、次のようなオブジェクトをメモリから解放するにはどうすれば良いでしょうか?


function Diagram(index, code, prev, next){
    ・・・
    this.routeLayer = document.createElementNS(svgns, "g");
    ・・・
}

この Diagram は、その中で HTML 要素 routeLayer を作成しています。 Diagram のインスタンス diagram をメモリから解放するには、単に delete を実行するだけで良いのでしょうか。それとも、先に routeLayerremoveChild しなければならないのでしょうか。モヤモヤしたまま LongDriver を実装しています。

メモリの開放については、まだ疑問が有ります。 foo は何かオブジェクトを指しているとします。その時、


	delete foo

foo を開放するのでしょうか。それとも foo が指しているオブジェクトを開放して foo 自体はメモリに残るのでしょうか追記2

さらにモヤモヤが1つ。上記の diagram が必要無くなった時、 delete を使って解放すべきなのでしょうか。1つのインスタンスを強制的に開放したところで、どれだけのメリットが有るのか疑問です。それよりも、


    diagram = null;

として、後はガベージコレクションに任せた方が良いのではないかと思っています。どうなのでしょうか?

追記1: delete について誤解していたようです。 delete はどんなオブジェクトでも操作対象になるのではないようです。

追記2: プロパティを削除するだけで、参照先を削除するのではない。

2016年10月 8日 (土)

エンコード・デコードルーチンの実装(1)

LongDriver にプレイ図のエンコード・デコードルーチンを実装しました。現時点では1つのプレイ図にしか対応していませんが、いずれ複数のプレイ図を扱えるようにする予定です。その際、今回の実装が大幅に変更されるかも知れません。サンプルを示します。

プレイ図(プレイブック)を作成した後、 LongDriver の右上に有る [ Total ] をクリックするとメニューが表示されます。その中の [ Gen. Full Code ] をクリックするとプレイ図(プレイブック)がコード化されて小さなウィンドウに表示されます。それをコピーしてブログ等に貼り付けて使用します。上記画像がその例です。この画像も LongDriver そのものなので、この画像で操作することが出来ます。

ところで、このエンコード・デコードルーチンの実装では、いろいろ失敗が有り、また、モヤモヤしたまま取り敢えず実装してみたという箇所も有ります。

1つはリスト処理の初歩的な失敗です。リストの先頭と末尾の設定を勘違いして無限ループを引き起こしてしまいました。たとえば、リストの終端の設定が「設定1」になっているのに、「設定2」のつもりで「ループ」の実装をした訳です。その結果無限ループに入り、ブラウザを終了させる羽目になりました。


// 端末の設定1
    node.head.prev = node.tail;
    node.tail.next = node.head;

// 端末の設定2
    node.head.prev = null;
    node.tail.next = null;

// ループ
    for (target = node.head; target; target = target.next){
        ・・・
    }

2つ目の失敗は、1つのクラスを複数のファイルに分けたことです。そのせいで、xx は定義されていないというエラーが出ました。 LongDriver は、まずプレイ図を作成するルーチンを LDoperate.js として作成し、次にそのプレイ図をエンコードするルーチンを LDencode.js とし、そしてデコードのルーチンを LDdecode.js としました。この3つのファイルに跨って Diagram というクラスを記述していました。それで未定義のエラーが発生した訳です。同一ファイル内だとメソッドは前方参照でも良いので、ファイルを分けることに問題が有ることを認識していませんでしたが、実際はダメだった訳です。 C の extern のようなものが JavaScript に無いか検索したのですが、どうも無さそうです。最終的には、 JavaScript ファイルは1つにまとめる予定なのですが、動作確認&デバッグの間はファイルを分けておくつもりでした。しかたないので、エラーが出ないように一部のファイルはまとめました。

モヤモヤに関しては、日を改めて書くことにします。

P.S.1
LongDriver の読み込み時に、メニューボタンが表示されないことが有りますが、その時は再読み込みをすると良くなるようです。これも複数ファイルの組み合わせに原因が有るのかも知れません。

P.S.2
いずれはタブレットに対応する予定ですが、現時点では未対応です。取り敢えず、PCで動くものを作ることを優先させます。

« 2016年9月 | トップページ | 2016年11月 »