カテゴリー「フットボールのプレイ図ソフト」の21件の記事

2017年2月26日 (日)

プレイルートに曲線を導入

LongDriver のプレイルートで曲線を使用できるようにしました。曲線は2次のベジェ曲線(放物線)です。プレイルートのノードにフォーカスが当たっている時にメニュー [Each][Curve/Straight] をクリックするとフォーカスの当たっていたノードがベジェ曲線の制御点になるような曲線が描画されます。下記サンプルで4番の選手の2つ目のノードが制御点になっています(画像下側の選手マーク4番を繰り返しクリックするとノードが順次表示されます)。制御点のノードにフォーカスを当てて再度 [Each][Curve/Straight] をクリックすると通常の直線に戻ります。

曲線化とプレイ図のエンコードはすんなり出来たのですが、デコードで暴走させてしまいました。デコードの関数内に曲線用のルーチンを追加したところ、プレイ図コードのスキャン位置が合わなくなり、それが原因で暴走しました。ルーチンを追加したくらいで暴走するようなプログラムを書いていてはダメですね。

ところで、ブレイクポイントを設定してデバッグしていた時に気付いたのですが、「var foo;」のような変数宣言にはブレイクポイントを設定できないのですね。これは、最初に構文解析したら、その後の実行では変数宣言はスキャンしないということでしょうか? 今まで、このような変数宣言はループの外に記述していたのですが、実行中にスキャンしないのなら、ループの内側に記述しても効率は落ちないことになります。


    var foo;		// ループの内側↓に入れても効率は落ちない?
    for (・・・){
        var bar;
        ・・・
    }

ちなみに、最近の JavaScript では var よりも let の方が良いとか言われていますが、馴染みが薄いので、今回は let は使っていません。

なお、LongDriver を読み込む時、ブラウザによってはキャッシュのせいで古い LongDriver を実行することがあるようです。 Chrome では、再読み込みをしてもキャッシュが効いていてダメでした。新しい LongDriver を実行するには、 Chrome の再読み込みマークを右クリックして「ハード再読み込み」を実行する必要がありました。

2016年11月26日 (土)

インターネットで見つけたプレイ図ソフト

アメリカンフットボールのプレイ図(プレイブック)ソフトを検索してみたところ、図のようなソフトを見つけました。アドレスは
http://footballtactics.net/playbook.html
です。

Pbsoft_alt

私のようなデザインのセンスの無い者の作った LongDriver よりも綺麗な絵になっています。

このソフトを扱っているメインページは
http://footballtactics.net/
で、サッカー用ソフトは完成しているようですが、アメリカンフットボール用は途中で止まっているのか、完成には至っていないようです。このサイトに掲載されている 「documentation」 はサッカー用なので注意が必要です。

さて、 LongDriver を、この footballtactics と比較すると次のような違いが有りました。

まず、footballtactics はデータや画像をローカルファイルに保存できるのに対して LongDriver は出来ません。方法が分からないので、画像の取得は PrtScr キーを使うという姑息な手段を使っています。

次に、 footballtactics はフィールドの向きを変えられるのに、 LongDriver は出来ません。当初、 LongDriver も変えられるようにする積りでいたのですが、気が向いたらやるということにして、未だ実装していません。

3つ目は、 footballtactics はプレイルートに曲線が使えるのに対して、 LongDriver は出来ません。これも当初は予定していたのですが LongDriver では未実装です。モチベーションが上がったら実装するかも知れません(追記2:モチベーションが上がったので曲線も表示できるようにしました)。

4つ目の違いは初期状態に関してです。 footballtactics の初期状態は選手が居ませんが、 LongDriver はデフォルトで基本的な(と思われる)フォーメーションに配置されます。それは、ゼロの状態から22人を配置するのは骨が折れるので、前もって配置しておいた方が良いだろうと考えたからです。

5つ目は、 footballtactics は単一のプレイ図しか扱えないのに対して、 LongDriver は複数のプレイ図を扱えます。これはビリヤードの配置図ソフト FlatTable からの流れとして当然の仕様です。因みに、複数のプレイ図を扱えることが LongDriver という名前の由来です。

6つ目の違いはデータの共有方法についてです。 footballtactics ではこのサイトがサーバーとなってデータを保管し、それをダウンロードすることでデータの共有を行っています。この方法だと、ブログではただの画像を表示するに止まるため、ブログのみで footballtactics のデータを再利用(共有)することは出来ません。そのため footballtactics ではサーバーを使ってデータ共有をサポートしているのだと思われます。それに対して LongDriver では、ブログに貼り付けたデータを利用できるようになっています。

そして最後に footballtactics は Flash で出来ているのに対して LongDriver は SVG+JavaScript で出来ている点が違います。 Flash はいつまで大丈夫なのでしょうか?

追記1:
6番目の項目のデータの共有は footballtactics のサッカー版では可能ですが、アメリカンフットボール版は未実装ではないかと思われます。

2016年11月21日 (月)

SVG の変換行列(CTM)

LongDriver の動作確認をしていて、選手マークをドラッグする時、マークの移動量とマウスの移動量が異なることに気付きました。試しに下に表示している LongDriver でマークをドラッグして下さい。マウスに比べてマークの移動量の小さいことが分かります。

LongDriver では、マークの移動量 deltX, deltY は、次の式で決定しています。


    deltX = (evt.clientX - pad.prevX);	// 右辺はマウスの移動量
    deltY = (evt.clientY - pad.prevY);
    ・・・
    var x = Number(target.getAttribute("x")) + deltX;
    var y = Number(target.getAttribute("y")) + deltY;
    target.setAttribute("x", x);	// target は選手マークを指す
    target.setAttribute("y", y);

このように、マウスの移動量をそのままマークの移動量としているのに、何故両者は一致しないのでしょうか?この原因は LongDriver が縮小表示され、その座標系とブラウザの座標系のスケールが異なることにあります。マウスの位置を表す evt.clientX や、それから得られる deltX 等はブラウザの座標系での値です。しかし target.setAttribute("x", x) を実行する時は LongDriver の座標系に基づいた点が選ばれるのです。そして LongDriver の座標系は縮小されているためマウスの移動量とマークの移動量が異なるのです。

SVG では、表示の大きさが変更された場合、その情報が変換行列 CTM に保存されます。 CTM は3×3行列で、新・旧座標を次の関係で繋ぎます。 \[ \begin{pmatrix} x_{prev} \\ y_{prev} \\ 1 \end{pmatrix} = \begin{pmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} x_{curr} \\ y_{curr} \\ 1 \end{pmatrix} \]

この式で左辺は旧座標系での値、右辺が新座標系での値だということに気を付けて下さい。今の場合、旧座標系とは LongDriver の初期座標系(すなわち、縮小前の座標系)のことです。この初期座標系はブラウザの座標系に一致しています。画面に表示されるのは初期座標系に換算されたものです。

上に表示している LongDriver を例に取ると CTM は \[ \rm{CTM} = \begin{pmatrix} 0.602 & 0 & 0 \\ 0 & 0.602 & 7.229 \\ 0 & 0 & 1 \end{pmatrix} \] になっています。ここで、マウスの移動量を新座標系の LongDriver に代入すると約 0.602 倍され初期座標系での移動量に換算して表示されます。逆の言い方をすると、マウスとマークの移動を一致させるには、マウスの移動量を 0.602 で割った値を選手マークの移動量とすると良いことになります。

さて、ここからは余談ですが、 CTM の成分の名前が \[ \begin{pmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{pmatrix} \] の並びになっているのは、なんとも気持ち悪いものです。何故、 \[ \begin{pmatrix} a & b & c \\ d & e & f \\ 0 & 0 & 1 \end{pmatrix} \] にしなかったのでしょうかね。

2016年11月 5日 (土)

選手のマークの番号を変更可能にした

LongDriver は一段落させた積りだったのですが、気になることが出て来て、それを改良しました。プレイ図を使っているブログを検索して見て回っていたら、実際のプレイを図示したものが有り、それには実際の選手の番号が記入されていました。LongDriver は、選手のマークの番号は固定されているので、そのような時に違和感が生じます。そこで、選手のマークの番号を変更できるようにしました。

番号の変更は次のようにやります。まず、変更したい選手を選択してからメニューの EachRenumber をクリックするとメッセージボックスが出ます。そこで、そのメッセージボックスに2ケタもしくは1ケタの文字列を入力します。すると、選択した選手のマークに付いている番号が新しいものに変わります。数字に限らずアルファベットも可能です。ちなみに、漢字を入力した場合の動作は保証しません。

サンプルを示します。今までの記事に載せたサンプルと番号が違うのが分かります。

2016年11月 4日 (金)

LongDriver のプレイブックコード

LongDriver のメニューボタン [Total][Gen. Full Code] をクリックするとプレイブック全体のコードが生成されるわけですが、そのプレイブックコードについて説明します。

アメリカンフットボールのプレイ図を表示する目的で LongDriver にアクセスする場合、表示したいプレイブックのコードを URL のクエリとして付加してアクセスます。上記手順で生成されるコードはクエリ部分を含む URL 全体です。なお、ここではクエリ部分に相当するものをプレイブックコードと呼ぶことにします。

プレイブックコードは次のような構成になっています。

プレイブックコード:
PB={12xxxx}{1プレイのコード.}・・・{1プレイのコード.}
1プレイのコード:
{21}{各選手のルート}・・・{各選手のルート}
各選手のルート:
y{座標}・・・{座標}
座標:
{36}{xxxx}

  • {12xxxx} は初期表示におけるフィールドのオフセットで、これが無い場合、初期表示はセンタースポットが画像の中心になります。 xxxx はオフセット値です。オフセット値は下に示している座標値と同じ仕様です。
  • 1プレイのコードの直後には「.」を付けます。
  • {21} は初期表示のプレイ図です。プレイブックが複数のプレイ図から成っている場合に、{21} の付いているプレイコードに対応するプレイ図が最初に表示されます。
  • y は選手とボールを表します。オフェンスは M~W、 ディフェンスは m~w です。ボールは X です。
  • 座標値は x-座標、y-座標とも2ケタのアルファベットで表され、x-座標、y-座標の順で並びます。例外的に 0 に対応する座標は 00 で表されます。
  • 矢印を表示する場合 {36} が座標値の前に付きます。

2016年11月 3日 (木)

LongDriver の公開

LongDriver の操作説明書を作成しました。メニューボタン [Total][Help] をクリックすると操作説明のページが別タブで開きます。

これで LongDriver の作成は一区切り付けたいと思います。当初に予定していた機能として、フィールドの上下の入れ替えやフィールドの拡大・縮小が有りますが、それらは気が向いたら追々やって行くということにします。

LongDriver は次の URL で公開しています。
http://spacelike.in.coocan.jp/football/LongDriver.svg

この LongDriver で作成したプレイブックを利用するには Alt+PrtScr でウィンドウを画像としてクリップボードにコピーして、それを使うか、あるいは LongDriver で生成した URL 付きのプレイブックコードを次のようにすると良いでしょう。(エンコード・デコードルーチンの実装(2) 参照)


<iframe src="プレイブックコード " width=" " height="高さ "></iframe>

ここに、高さ はブログ上の表示したい幅と高さです。

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);    不要になった

    ・・・
}